Кілька моделей на виду


302

Я хочу мати 2 моделі на одному перегляді. Сторінка містить і LoginViewModelі RegisterViewModel.

напр

public class LoginViewModel
{
    public string Email { get; set; }
    public string Password { get; set; }
}

public class RegisterViewModel
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
}

Чи потрібно мені зробити ще один ViewModel, який містить ці 2 ViewModels?

public BigViewModel
{
    public LoginViewModel LoginViewModel{get; set;}
    public RegisterViewModel RegisterViewModel {get; set;}
}

Мені потрібні атрибути перевірки, щоб перенести їх у подання. Ось чому мені потрібні ViewModels.

Чи не існує іншого способу, наприклад (без BigViewModel):

 @model ViewModel.RegisterViewModel
 @using (Html.BeginForm("Login", "Auth", FormMethod.Post))
 {
        @Html.TextBoxFor(model => model.Name)
        @Html.TextBoxFor(model => model.Email)
        @Html.PasswordFor(model => model.Password)
 }

 @model ViewModel.LoginViewModel
 @using (Html.BeginForm("Login", "Auth", FormMethod.Post))
 {
        @Html.TextBoxFor(model => model.Email)
        @Html.PasswordFor(model => model.Password)
 }


1
@saeed serpooshan, дякую тобі за посилання з різними варіантами, через 4 роки ти опублікував коментар, і це мені допомогло, я просто використовував ViewBagдля кожного на перегляд, прекрасно працює
shaijut

@stom Просто FYI: автор публікації завжди отримує сповіщення, але якщо ви хочете повідомити когось іншого, вам потрібно поставити @перед їх іменем, як я це робив тут.
jpaugh

Відповіді:


260

Існує маса способів ...

  1. зі своїм BigViewModel ви робите:

    @model BigViewModel    
    @using(Html.BeginForm()) {
        @Html.EditorFor(o => o.LoginViewModel.Email)
        ...
    }
  2. ви можете створити 2 додаткові перегляди

    Login.cshtml

    @model ViewModel.LoginViewModel
    @using (Html.BeginForm("Login", "Auth", FormMethod.Post))
    {
        @Html.TextBoxFor(model => model.Email)
        @Html.PasswordFor(model => model.Password)
    }

    і register.cshtml те саме

    після створення ви повинні відобразити їх у головному вікні та передати їм viewmodel / viewdata

    тож могло бути так:

    @{Html.RenderPartial("login", ViewBag.Login);}
    @{Html.RenderPartial("register", ViewBag.Register);}

    або

    @{Html.RenderPartial("login", Model.LoginViewModel)}
    @{Html.RenderPartial("register", Model.RegisterViewModel)}
  3. з використанням Ajax частини вашого веб-сайту стають більш незалежними

  4. iframes, але, мабуть, це не так


2
Це проблема, якщо 2 текстових поля мають одне ім’я на формі через використання часткових переглядів?
Шон Мклін

2
Ні, слід чітко клацнути на самому елементі, використовуючи щось на кшталт firebug (на firefox), і ви побачите щось на зразок id = "LoginViewModel_Email" name = "LoginViewModel.Email", тож насправді вони унікальні! Модель перегляду повинна бути тією, що вам потрібно, просто опублікуйте кожну сторінку за іншою URL-адресою, і ви повинні бути добре
Харун

@Lol coder насправді це були б 2 форми, по одній для кожної viewmodel, але в будь-якому випадку, якщо у вас буде 2 або 3 або більше з тим самим іменем, ви просто отримаєте масив з цим ім'ям на стороні сервера (якщо ви помістите його в парами способу після дії)
Ому,

@Chuck Norris Я використовую asp.net mvc 4 і застосував вашу методику часткового перегляду, але @Html.RenderActionповідомляє про помилку, що Expression повинен повернути значення
Deeptechtons

1
Чи можете ви пояснити, як це працює? Я розумію, що це вирішення проблеми, але я не можу її осягнути. У мене є подібний (дещо інший) приклад цієї проблеми, і я не можу зрозуміти, як її обійти.
Андре

127

Я б рекомендував використовувати Html.RenderActionта PartialViewResults для цього; це дозволить відображати одні й ті самі дані, але кожен частковий вигляд все одно матиме єдину модель перегляду та позбавляє від потребиBigViewModel

Тож ваш погляд містить щось таке:

@Html.RenderAction("Login")
@Html.RenderAction("Register")

Де Login& Registerобидві дії у вашому контролері визначені таким чином:

public PartialViewResult Login( )
{
    return PartialView( "Login", new LoginViewModel() );
}

public PartialViewResult Register( )
{
    return PartialView( "Register", new RegisterViewModel() );
}

LoginІ Registerпотім буде користувача елементи управління , що знаходяться або в поточній папці або в папці Shared і хотіли б що - щось на зразок цього:

/Views/Shared/Login.cshtml: (або /Views/MyView/Login.cshtml)

@model LoginViewModel
@using (Html.BeginForm("Login", "Auth", FormMethod.Post))
{
    @Html.TextBoxFor(model => model.Email)
    @Html.PasswordFor(model => model.Password)
}

/Views/Shared/Register.cshtml: (або /Views/MyView/Register.cshtml)

@model ViewModel.RegisterViewModel
@using (Html.BeginForm("Login", "Auth", FormMethod.Post))
{
    @Html.TextBoxFor(model => model.Name)
    @Html.TextBoxFor(model => model.Email)
    @Html.PasswordFor(model => model.Password)
}

І там у вас є єдиний контролер дії, перегляду та перегляду файлів для кожної дії з кожною абсолютно чіткою і не покладається одна на одну ні на що.


4
Це має багато сенсу з точки зору проектування, але з точки зору ефективності, чи не потрібно проходити 3 повних циклу циклу mvc? stackoverflow.com/questions/719027/renderaction-renderpartial/…
Шон Мклін

6
Так, ви правильно: це спричиняє додатковий повний цикл MVC для кожного RenderAction. Я завжди забуваю його частину пакету ф'ючерсів, оскільки мій проект завжди включає цей dll за замовчуванням. Це дійсно до переваг та вимог щодо того, чи варто додаткові цикли mvc розмежувати, що це дає з боку дизайну. Ви багато разів можете кешувати RenderActionрезультати, тому єдине враження, яке ви отримаєте, - це невелика додаткова обробка через фабрику контролерів.
TheRightChoyce

Я реалізував вище, що мені не вистачає? Будь ласка допомогу: stackoverflow.com/questions/9677818 / ...
diegohb

О ні! Це прекрасно працювало для мене прямо з воріт. Я будую внутрішній сайт із лише кількома користувачами ... тому ефективність не є моєю серйозною турботою. ДЯКУЮ ТОБІ!
Дерек Евермор

1
Довелося використовувати фігурні дужки, щоб працювати PartialView.
null

113

Іншим способом є використання:

@model Tuple<LoginViewModel,RegisterViewModel>

Я пояснив, як використовувати цей метод як у поданні, так і в контролері для іншого прикладу: Дві моделі в одному поданні в ASP MVC 3

У вашому випадку ви можете реалізувати це за допомогою наступного коду:

На виду:

@using YourProjectNamespace.Models;
@model Tuple<LoginViewModel,RegisterViewModel>

@using (Html.BeginForm("Login1", "Auth", FormMethod.Post))
{
        @Html.TextBoxFor(tuple => tuple.Item2.Name, new {@Name="Name"})
        @Html.TextBoxFor(tuple => tuple.Item2.Email, new {@Name="Email"})
        @Html.PasswordFor(tuple => tuple.Item2.Password, new {@Name="Password"})
}

@using (Html.BeginForm("Login2", "Auth", FormMethod.Post))
{
        @Html.TextBoxFor(tuple => tuple.Item1.Email, new {@Name="Email"})
        @Html.PasswordFor(tuple => tuple.Item1.Password, new {@Name="Password"})
}

Зверніть увагу, що я вручну змінив атрибути Name для кожного ресурсу під час створення форми. Це потрібно зробити, інакше він не буде належним чином відображений у параметрі методу типу типу, коли значення надсилаються до пов'язаного методу для обробки. Я б запропонував використовувати окремі методи для обробки цих форм окремо, для цього прикладу я використовував методи Login1 та Login2. Для методу Login1 потрібно мати параметр типу RegisterViewModel, а для Login2 потрібен параметр типу LoginViewModel.

якщо потрібне посилання дії, ви можете використовувати:

@Html.ActionLink("Edit", "Edit", new { id=Model.Item1.Id })

у методі контролера для перегляду потрібно створити змінну типу Tuple та передати її в представлення.

Приклад:

public ActionResult Details()
{
    var tuple = new Tuple<LoginViewModel, RegisterViewModel>(new LoginViewModel(),new RegisterViewModel());
    return View(tuple);
}

або ви можете заповнити два екземпляри LoginViewModel та RegisterViewModel значеннями, а потім передати їх перегляду.


Це був чудовий спосіб впоратися, дякую! Зробив те, що мені потрібно.
Тодд Девіс,

Я спробував це, але якщо я використовую EditorForабо HiddenFor(що в ідеалі те, що я хочу використовувати), властивості моделі не встановлюються, коли викликаються методи Login1/ Login2контролер. Імовірно, @Name=картографування ігнорується. Чи HiddenForпотрібен якийсь інший трюк для цієї ситуації?
Гері Чапман

1
Це взагалі не буде працювати - ви не можете прив’язатись до моделі, коли форму буде подано

@Hamid Спасибі Хаміде, для новачка в MVC це була найпростіша відповідь для мене. Дякую.
Harold_Finch

Як ви зв'язали модель при поданні форми?
Мохаммед Нурелдін

28

Використовуйте модель перегляду, яка містить кілька моделей перегляду:

   namespace MyProject.Web.ViewModels
   {
      public class UserViewModel
      {
          public UserDto User { get; set; }
          public ProductDto Product { get; set; }
          public AddressDto Address { get; set; }
      }
   }

На ваш погляд:

  @model MyProject.Web.ViewModels.UserViewModel

  @Html.LabelFor(model => model.User.UserName)
  @Html.LabelFor(model => model.Product.ProductName)
  @Html.LabelFor(model => model.Address.StreetName)

1
Це чудове рішення, і перевірка моделі все ще працює без жодних труднощів. Дякую!
AFM-Horizon

11

Чи потрібно мені зробити інший вигляд, який містить ці 2 подання?

Відповідь: Ні

Чи не існує іншого способу, як-от (без BigViewModel):

Так , ви можете використовувати Tuple (приносить магію з огляду на наявність декількох моделей).

Код:

 @model Tuple<LoginViewModel, RegisterViewModel>


    @using (Html.BeginForm("Login", "Auth", FormMethod.Post))
    {
     @Html.TextBoxFor(tuple=> tuple.Item.Name)
     @Html.TextBoxFor(tuple=> tuple.Item.Email)
     @Html.PasswordFor(tuple=> tuple.Item.Password)
    }


    @using (Html.BeginForm("Login", "Auth", FormMethod.Post))
     {
      @Html.TextBoxFor(tuple=> tuple.Item1.Email)
      @Html.PasswordFor(tuple=> tuple.Item1.Password)
     }

Невже це не буде належним чином відображатись до контролера, який приймав форму? Я думав, що буде шукати "Елемент" у вашому випадку, перш ніж шукати "ім'я".
SolidSnake4444

7

Додайте цей ModelCollection.cs до своїх моделей

using System;
using System.Collections.Generic;

namespace ModelContainer
{
  public class ModelCollection
  {
   private Dictionary<Type, object> models = new Dictionary<Type, object>();

   public void AddModel<T>(T t)
   {
      models.Add(t.GetType(), t);
   }

   public T GetModel<T>()
   {
     return (T)models[typeof(T)];
   }
 }
}

Контролер:

public class SampleController : Controller
{
  public ActionResult Index()
  {
    var model1 = new Model1();
    var model2 = new Model2();
    var model3 = new Model3();

    // Do something

    var modelCollection = new ModelCollection();
    modelCollection.AddModel(model1);
    modelCollection.AddModel(model2);
    modelCollection.AddModel(model3);
    return View(modelCollection);
  }
}

Вид:

enter code here
@using Models
@model ModelCollection

@{
  ViewBag.Title = "Model1: " + ((Model.GetModel<Model1>()).Name);
}

<h2>Model2: @((Model.GetModel<Model2>()).Number</h2>

@((Model.GetModel<Model3>()).SomeProperty

Мені подобається такий підхід, оскільки він дозволяє мені використовувати різні моделі в одному і тому ж огляді, не потребуючи того, щоб вони перетиналися.
Метт Малий

6

простий спосіб зробити це

ми можемо спочатку назвати всю модель

@using project.Models

потім надішліть свою модель із пакетом перегляду

// for list
ViewBag.Name = db.YourModel.ToList();

// for one
ViewBag.Name = db.YourModel.Find(id);

і з огляду

// for list
List<YourModel> Name = (List<YourModel>)ViewBag.Name ;

//for one
YourModel Name = (YourModel)ViewBag.Name ;

то легко використовуйте подібну модель


3

Моя порада - зробити велику модель перегляду:

public BigViewModel
{
    public LoginViewModel LoginViewModel{get; set;}
    public RegisterViewModel RegisterViewModel {get; set;}
}

У своєму Index.cshtml, якщо, наприклад, у вас є 2 частки:

@addTagHelper *,Microsoft.AspNetCore.Mvc.TagHelpers
@model .BigViewModel

@await Html.PartialAsync("_LoginViewPartial", Model.LoginViewModel)

@await Html.PartialAsync("_RegisterViewPartial ", Model.RegisterViewModel )

і в контролері:

model=new BigViewModel();
model.LoginViewModel=new LoginViewModel();
model.RegisterViewModel=new RegisterViewModel(); 

2

Хочу сказати, що моє рішення було подібним до відповіді, наданої на цій сторінці stackoverflow: ASP.NET MVC 4, кілька моделей в одному вигляді?

Однак у моєму випадку запит linq, який вони використовували у своєму контролері, для мене не працював.

Це сказаний запит:

var viewModels = 
        (from e in db.Engineers
         select new MyViewModel
         {
             Engineer = e,
             Elements = e.Elements,
         })
        .ToList();

Отже, "у вашому представленні просто вкажіть, що ви використовуєте колекцію моделей перегляду", для мене також не працювало.

Однак незначне коливання цього рішення спрацювало для мене. Ось моє рішення у випадку, якщо це допоможе комусь.

Ось моя модель подання, в якій я знаю, що у мене буде лише одна команда, але ця команда може мати кілька дощок (і у мене є папка ViewModels в папці Моделі btw, звідси і область імен):

namespace TaskBoard.Models.ViewModels
{
    public class TeamBoards
    {
        public Team Team { get; set; }
        public List<Board> Boards { get; set; }
    }
}

Тепер це мій контролер. Це найбільш суттєва відмінність рішення від посилання, на яке посилалося вище. Я створюю ViewModel, щоб надсилати до перегляду інакше.

public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }

            TeamBoards teamBoards = new TeamBoards();
            teamBoards.Boards = (from b in db.Boards
                                 where b.TeamId == id
                                 select b).ToList();
            teamBoards.Team = (from t in db.Teams
                               where t.TeamId == id
                               select t).FirstOrDefault();

            if (teamBoards == null)
            {
                return HttpNotFound();
            }
            return View(teamBoards);
        }

Тоді, на мою думку, я не вказую це як перелік. Я просто роблю "@model TaskBoard.Models.ViewModels.TeamBoards" Тоді мені потрібен лише для кожного, коли я переглядаю дошки Команди. Ось мій погляд:

@model TaskBoard.Models.ViewModels.TeamBoards

@{
    ViewBag.Title = "Details";
}

<h2>Details</h2>

<div>
    <h4>Team</h4>
    <hr />


    @Html.ActionLink("Create New Board", "Create", "Board", new { TeamId = @Model.Team.TeamId}, null)
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => Model.Team.Name)
        </dt>

        <dd>
            @Html.DisplayFor(model => Model.Team.Name)
            <ul>
                @foreach(var board in Model.Boards)
                { 
                    <li>@Html.DisplayFor(model => board.BoardName)</li>
                }
            </ul>
        </dd>

    </dl>
</div>
<p>
    @Html.ActionLink("Edit", "Edit", new { id = Model.Team.TeamId }) |
    @Html.ActionLink("Back to List", "Index")
</p>

Я досить новачок у ASP.NET MVC, тому мені знадобилося трохи часу, щоб зрозуміти це. Тож я сподіваюся, що ця публікація допоможе комусь розібратися у своєму проекті за коротший термін. :-)



1
  1. Створіть один новий клас у своїй моделі та властивості LoginViewModelта RegisterViewModel:

    public class UserDefinedModel() 
    {
        property a1 as LoginViewModel 
        property a2 as RegisterViewModel 
    }
  2. Потім використовуйте UserDefinedModelу своєму поданні.


1
так, це працює для мене. то я посилався на це так: (модель була оголошена у верхній частині огляду. Всередині неї було 2 моделі: профіль та електронний лист ... контролером Я заповнив одну з моделей, використовуючи повідомлення @notso нижче. Мені не потрібно було заповнювати інше, тому що я просто використовував це для введення інформації
JustJohn,

0

Це спрощений приклад з IEnumerable.

Я використовував дві моделі у поданні: форму з критеріями пошуку (модель SearchParams) та сітку для результатів, і я боровся з тим, як додати IEnumerable модель та іншу модель до того ж виду. Ось що я придумав, сподіваюся, що це комусь допоможе:

@using DelegatePortal.ViewModels;

@model SearchViewModel

@using (Html.BeginForm("Search", "Delegate", FormMethod.Post))
{

                Employee First Name
                @Html.EditorFor(model => model.SearchParams.FirstName,
new { htmlAttributes = new { @class = "form-control form-control-sm " } })

                <input type="submit" id="getResults" value="SEARCH" class="btn btn-primary btn-lg btn-block" />

}
<br />
    @(Html
        .Grid(Model.Delegates)
        .Build(columns =>
        {
            columns.Add(model => model.Id).Titled("Id").Css("collapse");
            columns.Add(model => model.LastName).Titled("Last Name");
            columns.Add(model => model.FirstName).Titled("First Name");
        })

...)

SearchViewModel.cs:

namespace DelegatePortal.ViewModels
{
    public class SearchViewModel
    {
        public IEnumerable<DelegatePortal.Models.DelegateView> Delegates { get; set; }

        public SearchParamsViewModel SearchParams { get; set; }
....

DelegateController.cs:

// GET: /Delegate/Search
    public ActionResult Search(String firstName)
    {
        SearchViewModel model = new SearchViewModel();
        model.Delegates = db.Set<DelegateView>();
        return View(model);
    }

    // POST: /Delegate/Search
    [HttpPost]
    public ActionResult Search(SearchParamsViewModel searchParams)
    {
        String firstName = searchParams.FirstName;
        SearchViewModel model = new SearchViewModel();

        if (firstName != null)
            model.Delegates = db.Set<DelegateView>().Where(x => x.FirstName == firstName);

        return View(model);
    }

SearchParamsViewModel.cs:

namespace DelegatePortal.ViewModels
{
    public class SearchParamsViewModel
    {
        public string FirstName { get; set; }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.