Múltiples modelos en una vista


302

Quiero tener 2 modelos en una vista. La página contiene ambos LoginViewModely RegisterViewModel.

p.ej

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

¿Necesito hacer otro ViewModel que contenga estos 2 ViewModels?

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

Necesito que los atributos de validación se presenten a la vista. Es por eso que necesito los ViewModels.

¿No hay otra forma como (sin el 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, muchas gracias por el enlace con diferentes opciones, después de 4 años publicó un comentario y me ayudó, solo lo usé ViewBagpara cada uno en la vista, funciona muy bien
shaijut

@stom Solo un FYI: el autor de la publicación siempre recibe una notificación, pero si desea notificar a alguien más, debe poner @su nombre al frente, como lo hice aquí.
jpaugh

Respuestas:


260

Hay muchas formas ...

  1. con tu BigViewModel haces:

    @model BigViewModel    
    @using(Html.BeginForm()) {
        @Html.EditorFor(o => o.LoginViewModel.Email)
        ...
    }
  2. puedes crear 2 vistas adicionales

    Login.cshtml

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

    y register.cshtml lo mismo

    después de la creación, debe representarlos en la vista principal y pasarles el modelo de vista / datos de vista

    entonces podría ser así:

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

    o

    @{Html.RenderPartial("login", Model.LoginViewModel)}
    @{Html.RenderPartial("register", Model.RegisterViewModel)}
  3. el uso de partes ajax de su sitio web se vuelve más independiente

  4. iframes, pero probablemente este no sea el caso


2
¿Es un problema si 2 cuadros de texto tienen el mismo nombre en el formulario debido al uso de vistas parciales?
Shawn Mclean

2
No, debe hacer un buen clic en el elemento en sí mismo usando algo como firebug (en firefox) y verá algo como id = "LoginViewModel_Email" name = "LoginViewModel.Email", ¡así que en realidad son únicos! Un modelo de vista debe ser lo que necesita, simplemente publicar cada página a una URL diferente y que debe estar bien
Haroon

@Lol codificador en realidad serían 2 formas, una para cada modelo de vista, pero de todos modos si tuviera 2 o 3 o más con el mismo nombre, obtendría una matriz con ese nombre en el lado del servidor (si lo coloca en los parámetros del método posterior a la acción)
Omu

@Chuck Norris Estoy usando asp.net mvc 4 e implementé su técnica de resultado parcial pero estoy @Html.RenderActioninformando un error que Expression debe devolver un valor
Deeptechtons

1
¿Puedes explicar cómo funciona esto? Entiendo que esta es una solución al problema, pero no puedo entenderlo. Tengo un ejemplo similar (ligeramente diferente) de este problema y no puedo entender cómo solucionarlo.
Andre

127

Recomiendo usar Html.RenderActiony PartialViewResults para lograr esto; le permitirá mostrar los mismos datos, pero cada vista parcial aún tendría un modelo de vista única y elimina la necesidad de unBigViewModel

Entonces su vista contiene algo como lo siguiente:

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

Donde Loginy Registerson ambas acciones en su controlador definidas de la siguiente manera:

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

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

El Login&Register luego serían controles de usuario que residen en la carpeta Ver actual o en la carpeta Compartida y quisiera algo como esto:

/Views/Shared/Login.cshtml: (o /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: (o /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)
}

Y allí tiene una única acción de controlador, ver y ver el archivo para cada acción con cada uno totalmente distinto y no depende el uno del otro para nada.


44
Esto tiene mucho sentido en términos de diseño, pero en términos de eficiencia, ¿no tiene que pasar por 3 ciclos completos del ciclo mvc? stackoverflow.com/questions/719027/renderaction-renderpartial/…
Shawn Mclean

66
Sí, tienes razón: provoca un ciclo MVC completo adicional para cada uno RenderAction. Siempre olvido su parte del paquete de futuros ya que mi proyecto siempre incluye ese dll por defecto. Realmente depende de los requisitos de preferencia y aplicación en cuanto a si los ciclos de mvc adicionales valen la separación que brinda en el lado del diseño. Muchas veces puede almacenar en caché los RenderActionresultados, por lo que el único golpe que recibe es el ligero procesamiento adicional a través de la fábrica de controladores.
TheRightChoyce

He implementado lo anterior ... ¿qué me estoy perdiendo? Por favor ayuda: stackoverflow.com/questions/9677818/…
diegohb

¡Santo cielo! Esto funcionó perfectamente para mí desde el principio. Estoy construyendo un sitio interno con solo un par de usuarios ... así que la eficiencia no es una preocupación real mía. ¡GRACIAS!
Derek Evermore

1
Tuve que usar llaves para que PartialView funcionara.
nulo

113

Otra forma es usar:

@model Tuple<LoginViewModel,RegisterViewModel>

He explicado cómo usar este método tanto en la vista como en el controlador para otro ejemplo: dos modelos en una vista en ASP MVC 3

En su caso, podría implementarlo utilizando el siguiente código:

En la vista:

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

Nota que he cambiado manualmente los atributos de Nombre para cada propiedad al crear el formulario. Esto debe hacerse, de lo contrario no se correlacionaría correctamente con el parámetro del modelo de tipo del método cuando los valores se envían al método asociado para su procesamiento. Sugeriría usar métodos separados para procesar estos formularios por separado, para este ejemplo utilicé los métodos Login1 e Login2. El método Login1 requiere tener un parámetro de tipo RegisterViewModel y Login2 requiere un parámetro de tipo LoginViewModel.

Si se requiere un enlace de acción, puede usar:

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

en el método del controlador para la vista, se debe crear una variable de tipo Tuple y luego pasarla a la vista.

Ejemplo:

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

o puede llenar las dos instancias de LoginViewModel y RegisterViewModel con valores y luego pasarlo a la vista.


Esta fue una excelente manera de manejarlo, ¡gracias! Hice lo que necesitaba.
Todd Davis

He intentado esto, pero si uso EditorForo HiddenFor(que es idealmente lo que quiero usar) las propiedades del modelo no se establecen cuando se invocan los métodos Login1/ Login2controller. Presumiblemente @Name=se está ignorando el mapeo. ¿ HiddenForRequiere algún otro truco para esta situación?
Gary Chapman el

1
Esto no funcionará en absoluto: no se puede vincular al modelo cuando se envía el formulario

@Hamid Gracias Hamid, para un novato en MVC, esta fue la respuesta más simplista para mí. Gracias.
Harold_Finch

¿Cómo vinculó el modelo al enviar el formulario?
Mohammed Noureldin

28

Use un modelo de vista que contenga múltiples modelos de vista:

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

En tu opinión:

  @model MyProject.Web.ViewModels.UserViewModel

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

1
Esta es una gran solución, y la validación del modelo aún funciona sin reparos. ¡Gracias!
AFM-Horizon

11

¿Necesito hacer otra vista que contenga estas 2 vistas?

Respuesta: no

¿No hay otra forma como (sin BigViewModel):

, puedes usar Tuple (trae magia a la vista teniendo múltiples modelos).

Código:

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

¿No se correlacionaría esto correctamente con el controlador que estaba aceptando el formulario? Pensé que buscaría "Artículo" en su caso antes de buscar "nombre".
SolidSnake4444

7

Agregue este ModelCollection.cs a sus modelos

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

Controlador:

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

La vista:

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

Me gusta este enfoque, ya que me permite usar diferentes modelos en la misma vista sin necesidad de que se crucen.
Matt Small

6

una manera simple de hacer eso

podemos llamar a todos los modelos primero

@using project.Models

luego envíe su modelo con viewbag

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

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

y a la vista

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

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

entonces use esto fácilmente como Modelo


3

Mi consejo es hacer un modelo de vista grande:

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

En su Index.cshtml, si por ejemplo tiene 2 parciales:

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

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

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

y en el controlador:

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

2

Quiero decir que mi solución fue como la respuesta proporcionada en esta página de stackoverflow: ASP.NET MVC 4, ¿varios modelos en una sola vista?

Sin embargo, en mi caso, la consulta linq que usaron en su controlador no funcionó para mí.

Esta es dicha consulta:

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

En consecuencia, "en su opinión, simplemente especifique que está utilizando una colección de modelos de vista" tampoco funcionó para mí.

Sin embargo, una ligera variación en esa solución funcionó para mí. Aquí está mi solución en caso de que esto ayude a alguien.

Aquí está mi modelo de vista en el que sé que solo tendré un equipo, pero ese equipo puede tener múltiples tableros (y tengo una carpeta ViewModels dentro de mi carpeta Modelos, por cierto, el espacio de nombres):

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

Ahora este es mi controlador. Esta es la diferencia más significativa de la solución en el enlace mencionado anteriormente. Construyo el ViewModel para enviarlo a la vista de manera diferente.

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

Entonces, en mi opinión, no lo especifico como una lista. Simplemente hago "@model TaskBoard.Models.ViewModels.TeamBoards". Entonces solo necesito un para cada uno cuando itero sobre los tableros del equipo. Aquí está mi punto de vista:

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

Soy bastante nuevo en ASP.NET MVC, por lo que me tomó un tiempo resolver esto. Entonces, espero que esta publicación ayude a alguien a resolver su proyecto en un plazo más corto. :-)



1
  1. Cree una nueva clase en su modelo y propiedades de LoginViewModely RegisterViewModel:

    public class UserDefinedModel() 
    {
        property a1 as LoginViewModel 
        property a2 as RegisterViewModel 
    }
  2. Luego úsalo UserDefinedModelen tu punto de vista.


1
Sí, eso funciona para mí. luego lo mencioné así: (el modelo se declaró en la parte superior de la vista. Había 2 modelos en su interior: perfil y correo electrónico...... @ Html.DisplayNameFor (model => model.profile.BlackoutBegin) En el controlador llené uno de los modelos usando @notso post a continuación. No necesitaba llenar el otro porque solo lo estaba usando para una entrada.
JustJohn

0

Este es un ejemplo simplificado con IEnumerable.

Estaba usando dos modelos en la vista: un formulario con criterios de búsqueda (modelo SearchParams) y una cuadrícula de resultados, y luché con la forma de agregar el modelo IEnumerable y el otro modelo en la misma vista. Esto es lo que se me ocurrió, espero que esto ayude a alguien:

@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; }
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.