Più modelli in una vista


302

Voglio avere 2 modelli in una vista. La pagina contiene sia LoginViewModele RegisterViewModel.

per esempio

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

Devo creare un altro ViewModel che contenga questi 2 ViewModel?

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

Ho bisogno che gli attributi di validazione vengano portati avanti alla vista. Questo è il motivo per cui ho bisogno dei ViewModels.

Non c'è un altro modo come (senza il 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, grazie mille per il collegamento con diverse opzioni, dopo 4 anni hai pubblicato un commento e mi ha aiutato, ho appena usato ViewBagper ognuno nella vista, funziona benissimo
shaijut

@stom Solo un FYI: l'autore del post riceve sempre una notifica, ma se vuoi avvisare qualcun altro, devi mettere il @loro nome, come ho fatto qui.
jpaugh

Risposte:


260

Ci sono molti modi ...

  1. con il tuo BigViewModel fai:

    @model BigViewModel    
    @using(Html.BeginForm()) {
        @Html.EditorFor(o => o.LoginViewModel.Email)
        ...
    }
    
  2. puoi creare 2 visualizzazioni aggiuntive

    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 stessa cosa

    dopo la creazione devi renderli nella vista principale e passare loro il viewmodel / viewdata

    quindi potrebbe essere così:

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

    o

    @{Html.RenderPartial("login", Model.LoginViewModel)}
    @{Html.RenderPartial("register", Model.RegisterViewModel)}
    
  3. l'utilizzo di ajax parti del tuo sito Web diventa più indipendente

  4. iframes, ma probabilmente non è così


2
È un problema se 2 caselle di testo hanno lo stesso nome sul modulo a causa dell'utilizzo delle viste parziali?
Shawn Mclean,

2
No, dovresti fare clic sull'elemento stesso usando qualcosa come firebug (su firefox) e vedrai qualcosa come id = "LoginViewModel_Email" name = "LoginViewModel.Email", quindi in realtà sono unici! Un modello di visualizzazione dovrebbe essere quello che ti serve, basta pubblicare ogni pagina su un URL diverso e dovresti andare bene
Haroon,

Il programmatore di @Lol in realtà sarebbe di 2 moduli, uno per ogni viewmodel, ma comunque se avessi 2 o 3 o più con lo stesso nome otterrai semplicemente un array con quel nome sul lato server (se lo metti nei parametri del metodo post azione)
Omu,

@Chuck Norris Sto usando asp.net mvc 4 e ho implementato la tua tecnica di visualizzazione parziale ma sto @Html.RenderActionsegnalando un errore che Expression deve restituire un valore
Deeptechtons,

1
Puoi spiegare come funziona? Capisco che questa sia una soluzione al problema, ma non riesco a capirlo. Ho un esempio simile (leggermente diverso) di questo problema e non riesco a capire come risolverlo.
Andre

127

Consiglierei di usare Html.RenderActionPartialViewResults per farlo; ti permetterà di visualizzare gli stessi dati, ma ogni vista parziale avrebbe comunque un modello a vista singola e rimuove la necessità di aBigViewModel

Quindi la tua vista contiene qualcosa di simile al seguente:

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

Dove Logine Registersono entrambe le azioni nel controller definite come le seguenti:

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

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

Il Login& Registersarebbe allora controlli utente che risiedono sia nella cartella di vista corrente, o nella cartella condivisa e vorrebbe qualcosa di simile:

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

E lì hai una singola azione del controller, visualizza e visualizza il file per ogni azione con ognuno totalmente distinto e non dipende l'uno dall'altro per nulla.


4
Ciò ha molto senso in termini di progettazione, ma in termini di efficienza, non deve passare attraverso 3 cicli completi del ciclo mvc? stackoverflow.com/questions/719027/renderaction-renderpartial/…
Shawn Mclean

6
Sì, hai ragione: provoca un ciclo MVC completo aggiuntivo per ciascuno RenderAction. Dimentico sempre la sua parte del pacchetto futures poiché il mio progetto include sempre quella dll di default. Sta davvero ai requisiti di preferenza e applicazione se i cicli mvc aggiuntivi valgono la separazione che offre dal punto di vista del design. Molte volte è possibile memorizzare nella cache i RenderActionrisultati, quindi l'unico colpo che si ottiene è la leggera elaborazione aggiuntiva tramite la fabbrica del controller.
TheRightChoyce,

Ho implementato quanto sopra .. cosa mi sto perdendo? Si prega di aiuto: stackoverflow.com/questions/9677818/...
diegohb

Merda santa! Questo ha funzionato perfettamente per me fin dal cancello. Sto costruendo un sito interno con solo un paio di utenti ... quindi l'efficienza non è una mia vera preoccupazione. GRAZIE!
Derek Evermore,

1
Ho dovuto usare le parentesi graffe per far funzionare PartialView.
null

113

Un altro modo è usare:

@model Tuple<LoginViewModel,RegisterViewModel>

Ho spiegato come utilizzare questo metodo sia nella vista che nel controller per un altro esempio: due modelli in una vista in ASP MVC 3

Nel tuo caso potresti implementarlo usando il seguente codice:

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

Si noti che ho modificato manualmente gli attributi Nome per ogni proprietà durante la creazione del modulo. Questo deve essere fatto, altrimenti non verrebbe correttamente mappato al parametro del metodo del modello di tipo quando i valori vengono inviati al metodo associato per l'elaborazione. Suggerirei di utilizzare metodi separati per elaborare questi moduli separatamente, per questo esempio ho usato i metodi Login1 e Login2. Il metodo Login1 richiede di avere un parametro di tipo RegisterViewModel e Login2 richiede un parametro di tipo LoginViewModel.

se è richiesto un actionlink è possibile utilizzare:

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

nel metodo del controller per la vista, è necessario creare una variabile di tipo Tupla e quindi passarla alla vista.

Esempio:

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

oppure è possibile riempire le due istanze di LoginViewModel e RegisterViewModel con valori e quindi passarlo alla vista.


Questo è stato un ottimo modo per gestirlo, grazie! Ho fatto quello di cui avevo bisogno.
Todd Davis,

Ho provato questo, ma se uso EditorForo HiddenFor(che è idealmente quello che voglio usare) le proprietà del modello non vengono impostate quando vengono chiamati i metodi Login1/ Login2controller. Presumibilmente la @Name=mappatura viene ignorata. Non HiddenForrichiede qualche altro trucco per questa situazione?
Gary Chapman,

1
Questo non funzionerà affatto - non è possibile associarsi al modello quando viene inviato il modulo

@Hamid Grazie Hamid, per un principiante in MVC, questa è stata la risposta più semplicistica per me. Grazie.
Harold_Finch

Come hai vincolato il modello quando hai inviato il modulo?
Mohammed Noureldin,

28

Utilizzare un modello di visualizzazione che contiene più modelli di visualizzazione:

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

Dal tuo punto di vista:

  @model MyProject.Web.ViewModels.UserViewModel

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

1
Questa è un'ottima soluzione e la validazione del modello funziona ancora senza scrupoli. Grazie!
AFM-Horizon,

11

Devo creare un'altra vista che contenga queste 2 viste?

Risposta: No

Non esiste un altro modo come (senza BigViewModel):

, puoi usare Tuple (porta la magia in vista avendo più modelli).

Codice:

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

Questo non sarebbe mappato correttamente al controller che stava accettando il modulo? Ho pensato che avrebbe cercato "Item" nel tuo caso prima di cercare "name".
SolidSnake4444

7

Aggiungi questo ModelCollection.cs ai tuoi modelli

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

controller:

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

Mi piace questo approccio poiché mi consente di utilizzare modelli diversi nella stessa vista senza doverli intersecare.
Matt Small

6

un modo semplice per farlo

possiamo chiamare prima tutti i modelli

@using project.Models

quindi invia il tuo modello con viewbag

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

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

e in vista

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

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

quindi usa facilmente questo modello


3

Il mio consiglio è di creare un modello di visualizzazione grande:

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

Nel tuo Index.cshtml, se per esempio hai 2 parziali:

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

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

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

e nel controller:

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

2

Voglio dire che la mia soluzione era come la risposta fornita in questa pagina di stackoverflow: ASP.NET MVC 4, più modelli in una vista?

Tuttavia, nel mio caso, la query linq che hanno usato nel loro controller non ha funzionato per me.

Questo è detto query:

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

Di conseguenza, "a tuo avviso, specifica che stai utilizzando una raccolta di modelli di vista" non ha funzionato neanche per me.

Tuttavia, una leggera variazione su quella soluzione ha funzionato per me. Ecco la mia soluzione nel caso questo aiuti qualcuno.

Ecco il mio modello di visualizzazione in cui so che avrò solo una squadra ma quella squadra potrebbe avere più schede (e ho una cartella ViewModels nella mia cartella Modelli tra cui, quindi lo spazio dei nomi):

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

Ora questo è il mio controller. Questa è la differenza più significativa dalla soluzione nel link sopra indicato. Costruisco ViewModel per inviarlo alla vista in modo diverso.

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

Quindi, a mio avviso, non lo specifico come elenco. Faccio solo "@model TaskBoard.Models.ViewModels.TeamBoards" Quindi ho solo bisogno di uno per ciascuno quando eseguo un'iterazione sui board del Team. Ecco il mio punto di 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>

Sono abbastanza nuovo in ASP.NET MVC, quindi mi ci è voluto un po 'di tempo per capirlo. Quindi, spero che questo post aiuti qualcuno a capirlo per il proprio progetto in tempi più brevi. :-)



1
  1. Crea una nuova classe nel tuo modello e nelle proprietà di LoginViewModele RegisterViewModel:

    public class UserDefinedModel() 
    {
        property a1 as LoginViewModel 
        property a2 as RegisterViewModel 
    }
  2. Quindi utilizzare UserDefinedModelnella vista.


1
si, funziona per me. poi ho fatto riferimento in questo modo: (il modello è stato dichiarato nella parte superiore della vista. Al suo interno c'erano 2 modelli: profilo e emailstuff........ .. il controller che ho riempito uno dei modelli usando @notso post di seguito. Non ho avuto bisogno di riempire l'altro perché lo stavo solo usando per un input
JustJohn

0

Questo è un esempio semplificato con IEnumerable.

Stavo usando due modelli nella vista: un modulo con criteri di ricerca (modello SearchParams) e una griglia per i risultati, e ho lottato con come aggiungere il modello IEnumerable e l'altro modello nella stessa vista. Ecco cosa mi è venuto in mente, spero che questo aiuti qualcuno:

@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; }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.