Несколько моделей в представлении


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 ViewModel?

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)
 }

3
Посмотрите это: codeproject.com/Articles/687061/…
S.Serpooshan

1
@saeed serpooshan, большое спасибо за ссылку с различными вариантами, после 4 лет вы оставили комментарий, и он помог мне, я просто использовал ViewBagдля каждого в представлении, отлично работает
shaijut

@stom Просто к сведению: автор сообщения всегда получает уведомление, но если вы хотите уведомить кого-то еще, вам нужно поставить @перед его именем, как я сделал здесь.
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-адресу, и у вас все будет хорошо
Haroon

@Lol-кодер, на самом деле это будет 2 формы, по одной для каждой модели представления, но в любом случае, если у вас будет 2 или 3 или более с тем же именем, вы просто получите массив с этим именем на стороне сервера (если вы поместите его в params метода пост-действия)
Ому,

@ Чак Норрис Я использую 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будет пользовательским элементом управления, находящимся либо в текущей папке View, либо в папке 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.
ноль

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"})
}

Обратите внимание, что я вручную изменил атрибуты имени для каждого свойства при построении формы. Это необходимо сделать, в противном случае он не будет должным образом сопоставлен с параметром метода модели типа при отправке значений в связанный метод для обработки. Я бы предложил использовать отдельные методы для обработки этих форм отдельно, в этом примере я использовал методы Login1 и Login2. Метод Login1 требует наличия параметра типа RegisterViewModel, а метод Login2 требует параметра типа LoginViewModel.

если требуется actionlink, вы можете использовать:

@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/ Login2controller. Предположительно, @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

затем отправьте вашу модель с viewbag

// 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

Я хочу сказать, что мое решение было похоже на ответ, представленный на этой странице: в ASP.NET MVC 4 несколько моделей в одном представлении?

Однако в моем случае запрос linq, который они использовали в своем контроллере, не работал для меня.

Это сказал запрос:

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

Следовательно, «по вашему мнению, просто укажите, что вы используете коллекцию моделей представлений», у меня тоже не получилось.

Тем не менее, небольшое изменение этого решения сработало для меня. Вот мое решение на случай, если это кому-нибудь поможет.

Вот моя модель представления, в которой я знаю, что у меня будет только одна команда, но у этой команды может быть несколько досок (и у меня есть папка ViewModels в моей папке Models, кстати, отсюда и пространство имен):

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
да, это работает для меня. затем я ссылался на него следующим образом: (модель была объявлена ​​в верхней части представления. Внутри нее было две модели: профиль и emailstuff..... @ Html.DisplayNameFor (model => model.profile.BlackoutBegin) контроллер, который я заполнил одну из моделей, используя пост @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.