Vários modelos em uma visão


302

Eu quero ter 2 modelos em uma visão. A página contém ambos LoginViewModele RegisterViewModel.

por exemplo

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

Preciso criar outro ViewModel que possua esses 2 ViewModels?

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

Eu preciso que os atributos de validação sejam antecipados para a exibição. É por isso que eu preciso dos ViewModels.

Não existe outra maneira como (sem o 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
serpooshan @saeed, muito obrigado para o link com diferentes opções, após 4 anos que você postou um comentário e me ajudou, eu só utilizado ViewBagpor cada um na vista, funciona muito bem
shaijut

@stom Apenas um FYI: o autor da postagem sempre recebe uma notificação, mas se você deseja notificar outra pessoa, precisa colocar @na frente do nome dela , como fiz aqui.
jpaugh

Respostas:


260

Há várias maneiras...

  1. com seu BigViewModel você faz:

    @model BigViewModel    
    @using(Html.BeginForm()) {
        @Html.EditorFor(o => o.LoginViewModel.Email)
        ...
    }
  2. você pode criar 2 visualizações adicionais

    Login.cshtml

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

    e register.cshtml a mesma coisa

    após a criação, você deve renderizá-los na visualização principal e transmitir a viewmodel / viewdata

    então poderia ser assim:

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

    ou

    @{Html.RenderPartial("login", Model.LoginViewModel)}
    @{Html.RenderPartial("register", Model.RegisterViewModel)}
  3. o uso de partes ajax do seu site se torna mais independente

  4. iframes, mas provavelmente este não é o caso


2
É um problema se duas caixas de texto tiverem o mesmo nome no formulário devido ao uso de visualizações parciais?
Shawn Mclean

2
Não, clique com o botão direito do mouse no elemento usando algo como firebug (no firefox) e você verá algo como id = "LoginViewModel_Email" name = "LoginViewModel.Email", portanto, na verdade, eles são únicos! Um modelo de vista deve ser o que você precisa, basta postar cada página para uma URL diferente e você deve estar bem
Haroon

@Lol coder, na verdade, seriam 2 formulários, um para cada viewmodel, mas de qualquer maneira, se você tivesse 2 ou 3 ou mais com o mesmo nome, obteria uma matriz com esse nome no lado do servidor (se você colocá-lo nos parâmetros do método pós-ação)
Omu

@Chuck Norris Eu estou usando asp.net MVC 4 e implementou sua técnica PartialViewResult mas @Html.RenderActionestá relatando um erro que Expression deve retornar um valor
Deeptechtons

1
Você pode explicar como isso funciona? Entendo que esta é uma solução para o problema, mas não consigo entender nada. Eu tenho um exemplo semelhante (um pouco diferente) desse problema e não consigo descobrir como contorná-lo.
187 Andre Andre

127

Eu recomendo usar Html.RenderActione PartialViewResults para fazer isso; permitirá que você exiba os mesmos dados, mas cada vista parcial ainda teria um modelo de vista único e elimina a necessidade de umBigViewModel

Portanto, sua visualização contém algo como o seguinte:

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

Onde Login& Registersão as duas ações no seu controlador definidas da seguinte forma:

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

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

Os Login& Registerseriam então controles de usuário que residem na pasta View atual ou na pasta Shared e gostaria algo como isto:

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

E aí você tem uma única ação do controlador, visualiza e exibe o arquivo para cada ação, sendo que cada um é totalmente distinto e não depende um do outro para nada.


4
Isso faz muito sentido em termos de design, mas em termos de eficiência, ele não precisa passar por três ciclos completos do ciclo mvc? stackoverflow.com/questions/719027/renderaction-renderpartial/…
Shawn Mclean

6
Sim, você está correto: isso causa um ciclo MVC completo adicional para cada um RenderAction. Eu sempre esqueço sua parte do pacote de futuros, pois meu projeto sempre inclui essa dll por padrão. É realmente de acordo com os requisitos de preferência e aplicação, se os ciclos adicionais de mvc valem a separação que ele oferece no lado do design. Muitas vezes você pode armazenar em cache os RenderActionresultados, portanto, a única ocorrência que você recebe é o pequeno processamento extra via fábrica do controlador.
TheRightChoyce

Eu implementei o acima .. o que estou perdendo? Por favor, ajude: stackoverflow.com/questions/9677818/…
diegohb

Puta merda! Isso funcionou perfeitamente para mim logo de cara. Estou construindo um site interno com apenas alguns usuários ... então a eficiência não é uma preocupação real minha. OBRIGADO!
Derek Evermore

1
Tinha que usar colchetes para fazer o PartialView funcionar.
null

113

Outra maneira é usar:

@model Tuple<LoginViewModel,RegisterViewModel>

Expliquei como usar esse método na exibição e no controlador para outro exemplo: Dois modelos em uma exibição no ASP MVC 3

No seu caso, você pode implementá-lo usando o seguinte código:

Na visualização:

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

Observe que eu alterei manualmente os atributos de nome para cada propriedade ao criar o formulário. Isso precisa ser feito, caso contrário, não seria mapeado corretamente para o parâmetro do tipo de modelo do método quando os valores forem enviados ao método associado para processamento. Eu sugeriria o uso de métodos separados para processar esses formulários separadamente; neste exemplo, usei os métodos Login1 e Login2. O método Login1 requer um parâmetro do tipo RegisterViewModel e Login2 requer um parâmetro do tipo LoginViewModel.

se um link de ação for necessário, você poderá usar:

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

no método do controlador para a exibição, uma variável do tipo Tupla precisa ser criada e depois passada para a exibição.

Exemplo:

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

ou você pode preencher as duas instâncias de LoginViewModel e RegisterViewModel com valores e depois passá-lo para a visualização.


Esta foi uma ótima maneira de lidar com isso, obrigado! Fiz o que eu precisava.
21413 Todd Davis

Eu tentei isso, mas se eu usar EditorForou HiddenFor(o que é ideal o que eu quero usar) as propriedades do modelo não serão definidas quando os métodos Login1/ Login2controller forem chamados. Presumivelmente, o @Name=mapeamento está sendo ignorado. Será que HiddenForexigem algum outro truque para esta situação?
Gary Chapman

1
Isso não funcionará de

@ Hamid Obrigado Hamid, por um novato no MVC, essa foi a resposta mais simplista para mim. Obrigado.
Harold_Finch

Como você vinculou o modelo ao enviar o formulário?
Mohammed Noureldin

28

Use um modelo de vista que contenha vários 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; }
      }
   }

Na sua opinião:

  @model MyProject.Web.ViewModels.UserViewModel

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

1
Essa é uma ótima solução e a validação do modelo ainda funciona sem escrúpulos. Obrigado!
AFM-Horizon

11

Preciso fazer outra visualização que contenha essas 2 visualizações?

Resposta: Não

Não existe outra maneira, como (sem o BigViewModel):

Sim , você pode usar Tupla (traz a mágica tendo em vista o modelo múltiplo).

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

Isso não seria mapeado corretamente para o controlador que estava aceitando o formulário? Eu pensei que procuraria "Item" no seu caso antes de procurar "nome".
SolidSnake4444

7

Adicione este ModelCollection.cs aos seus 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);
  }
}

A 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

Eu gosto dessa abordagem, pois ela me permite usar diferentes modelos na mesma visualização sem precisar interceptá-los.
Matt pequeno

6

uma maneira simples de fazer isso

podemos chamar todos os modelos primeiro

@using project.Models

então envie seu modelo com viewbag

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

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

e em vista

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

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

então use-o facilmente como o Model


3

Meu conselho é criar um modelo de visão ampla:

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

No seu Index.cshtml, se por exemplo você tiver 2 parciais:

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

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

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

e no controlador:

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

2

Quero dizer que minha solução foi a resposta fornecida nesta página de stackoverflow: ASP.NET MVC 4, vários modelos em uma exibição?

No entanto, no meu caso, a consulta linq que eles usaram em seu Controller não funcionou para mim.

Isto é dito consulta:

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

Consequentemente, "na sua opinião, apenas especifique que você está usando uma coleção de modelos de visualização" também não funcionou para mim.

No entanto, uma ligeira variação nessa solução funcionou para mim. Aqui está a minha solução, caso isso ajude alguém.

Aqui está o meu modelo de exibição no qual eu sei que terei apenas uma equipe, mas essa equipe pode ter várias placas (e eu tenho uma pasta ViewModels dentro da minha pasta Models btw, daí o namespace):

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

Agora este é o meu controlador. Essa é a diferença mais significativa da solução no link mencionado acima. Eu construo o ViewModel para enviar para o modo de exibição 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);
        }

Então, na minha opinião, não o especifico como uma lista. Eu apenas faço "@model TaskBoard.Models.ViewModels.TeamBoards" Então, só preciso de um para cada um quando iterar nos quadros da equipe. Aqui está a minha opinião:

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

Eu sou bastante novo no ASP.NET MVC, por isso demorei um pouco para descobrir isso. Espero que este post ajude alguém a descobrir o projeto em um prazo mais curto. :-)



1
  1. Crie uma nova classe em seu modelo e propriedades de LoginViewModele RegisterViewModel:

    public class UserDefinedModel() 
    {
        property a1 as LoginViewModel 
        property a2 as RegisterViewModel 
    }
  2. Então use UserDefinedModelna sua visão.


1
Sim, isso funciona para mim. referenciei-o assim: (o modelo foi declarado na parte superior da visualização. Havia dois modelos dentro dele: profile e emailstuff........ o controlador I preenchido um dos modelos usando @notso post abaixo Eu não precisa preencher o outro becuase Eu estava apenas usando-o para uma entrada..
JustJohn

0

Este é um exemplo simplificado com IEnumerable.

Eu estava usando dois modelos na exibição: um formulário com critérios de pesquisa (modelo SearchParams) e uma grade para resultados, e lutei com como adicionar o modelo IEnumerable e o outro modelo na mesma exibição. Aqui está o que eu vim com, espero que isso ajude alguém:

@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; }
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.