Plusieurs modèles dans une vue

302

Je veux avoir 2 modèles dans une vue. La page contient à la fois LoginViewModelet RegisterViewModel.

par exemple

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

Dois-je créer un autre ViewModel contenant ces 2 ViewModels?

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

J'ai besoin que les attributs de validation soient avancés dans la vue. C'est pourquoi j'ai besoin des ViewModels.

N'y a-t-il pas un autre moyen tel que (sans le 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)
 }
Shawn Mclean
la source
3
voir ceci: codeproject.com/Articles/687061/...
S.Serpooshan
1
@saeed serpooshan, merci beaucoup pour le lien avec différentes options, après 4 ans vous avez posté un commentaire et cela m'a aidé, je viens de l'utiliser ViewBagavec pour chacun dans la vue, fonctionne très bien
shaijut
@stom Just an FYI: l'auteur du message reçoit toujours une notification, mais si vous souhaitez informer quelqu'un d'autre, vous devez mettre @en face de son nom, comme je l'ai fait ici.
jpaugh

Réponses:

260

Il y a beaucoup de façons...

  1. avec votre BigViewModel vous faites:

    @model BigViewModel    
    @using(Html.BeginForm()) {
        @Html.EditorFor(o => o.LoginViewModel.Email)
        ...
    }
  2. vous pouvez créer 2 vues supplémentaires

    Login.cshtml

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

    et register.cshtml même chose

    après la création, vous devez les rendre dans la vue principale et leur passer le viewmodel / viewdata

    donc ça pourrait être comme ça:

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

    ou

    @{Html.RenderPartial("login", Model.LoginViewModel)}
    @{Html.RenderPartial("register", Model.RegisterViewModel)}
  3. utiliser des parties ajax de votre site Web devient plus indépendant

  4. iframes, mais ce n'est probablement pas le cas

Omu
la source
2
Est-ce un problème si 2 zones de texte ont le même nom sur le formulaire en raison de l'utilisation de vues partielles?
Shawn Mclean
2
Non, devrait être bien - cliquez sur l'élément lui-même en utilisant quelque chose comme firebug (sur firefox) et vous verrez quelque chose comme id = "LoginViewModel_Email" name = "LoginViewModel.Email", donc en réalité ils sont uniques! Un modèle de vue devrait être ce dont vous avez besoin, postez chaque page sur une URL différente et tout
ira
Le codeur @Lol en fait, ce serait 2 formes, une pour chaque modèle de vue, mais de toute façon si vous en avez 2 ou 3 ou plus avec le même nom, vous obtiendrez simplement un tableau avec ce nom côté serveur (si vous le mettez dans les paramètres de la méthode post-action)
Omu
@Chuck Norris J'utilise asp.net mvc 4 et j'ai implémenté votre technique partialviewresult mais je @Html.RenderActionsignale une erreur indiquant que l' expression doit renvoyer une valeur
Deeptechtons
1
Pouvez-vous expliquer comment cela fonctionne? Je comprends que c'est une solution au problème, mais je n'y comprends rien. J'ai un exemple similaire (légèrement différent) de ce problème et je ne peux pas comprendre comment le contourner.
Andre
127

Je recommanderais d'utiliser Html.RenderActionet PartialViewResults pour accomplir cela; il vous permettra d'afficher les mêmes données, mais chaque vue partielle aurait toujours un modèle de vue unique et supprime le besoin d'unBigViewModel

Votre vue contient donc quelque chose comme ceci:

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

Loginet Registerles deux actions de votre contrôleur sont-elles définies comme suit:

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

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

Le Login& Registerserait alors des contrôles utilisateur résidant soit dans le dossier View actuel, soit dans le dossier Shared et voudrait quelque chose comme ceci:

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

Et là, vous avez une seule action de contrôleur, affichez et affichez le fichier pour chaque action, chacune étant totalement distincte et ne dépendant les unes des autres pour rien.

TheRightChoyce
la source
4
Cela a beaucoup de sens en termes de conception, mais en termes d'efficacité, ne doit-il pas passer par 3 cycles complets du cycle mvc? stackoverflow.com/questions/719027/renderaction-renderpartial/…
Shawn Mclean
6
Oui, vous avez raison: cela provoque un cycle MVC complet supplémentaire pour chacun RenderAction. J'oublie toujours sa partie du pack futures car mon projet inclut toujours cette DLL par défaut. C'est vraiment à la préférence et aux exigences d'application de savoir si les cycles mvc supplémentaires valent la séparation qu'il donne du côté de la conception. Souvent, vous pouvez mettre en cache les RenderActionrésultats, donc le seul coup que vous prenez est le léger traitement supplémentaire via l'usine du contrôleur.
TheRightChoyce du
J'ai implémenté ce qui précède .. qu'est-ce qui me manque? S'il vous plaît aider: stackoverflow.com/questions/9677818/…
diegohb
Merde! Cela a parfaitement fonctionné pour moi dès la sortie de la porte. Je suis en train de construire un site interne avec seulement quelques utilisateurs ... donc l'efficacité n'est pas une réelle préoccupation pour moi. MERCI!
Derek Evermore
1
J'ai dû utiliser des accolades pour faire fonctionner PartialView.
null
113

Une autre façon consiste à utiliser:

@model Tuple<LoginViewModel,RegisterViewModel>

J'ai expliqué comment utiliser cette méthode à la fois dans la vue et le contrôleur pour un autre exemple: Deux modèles dans une vue dans ASP MVC 3

Dans votre cas, vous pouvez l'implémenter en utilisant le code suivant:

Dans la vue:

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

Notez que j'ai modifié manuellement les attributs de nom pour chaque propriété lors de la création du formulaire. Cela doit être fait, sinon cela ne serait pas correctement mappé au paramètre de type du modèle de la méthode lorsque les valeurs sont envoyées à la méthode associée pour le traitement. Je suggère d'utiliser des méthodes distinctes pour traiter ces formulaires séparément, pour cet exemple, j'ai utilisé les méthodes Login1 et Login2. La méthode Login1 nécessite d'avoir un paramètre de type RegisterViewModel et Login2 nécessite un paramètre de type LoginViewModel.

si un lien d'action est requis, vous pouvez utiliser:

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

dans la méthode du contrôleur pour la vue, une variable de type Tuple doit être créée puis transmise à la vue.

Exemple:

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

ou vous pouvez remplir les deux instances de LoginViewModel et RegisterViewModel avec des valeurs, puis les transmettre à la vue.

Hamid Tavakoli
la source
C'était une excellente façon de le gérer, merci! J'ai fait ce dont j'avais besoin.
Todd Davis
J'ai essayé cela, mais si j'utilise EditorForou HiddenFor(ce qui est idéalement ce que je veux utiliser), les propriétés du modèle ne sont pas définies lorsque les méthodes Login1/ Login2controller sont appelées. Vraisemblablement, le @Name=mappage est ignoré. Nécessite HiddenForune autre astuce pour cette situation?
Gary Chapman
1
Cela ne fonctionnera pas du tout - vous ne pouvez pas vous lier au modèle lorsque le formulaire est soumis
@Hamid Merci Hamid, pour un débutant en MVC, c'était la réponse la plus simpliste pour moi. Je vous remercie.
Harold_Finch
Comment avez-vous lié le modèle lors de la soumission du formulaire?
Mohammed Noureldin
28

Utilisez un modèle de vue qui contient plusieurs modèles de vue:

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

À votre avis:

  @model MyProject.Web.ViewModels.UserViewModel

  @Html.LabelFor(model => model.User.UserName)
  @Html.LabelFor(model => model.Product.ProductName)
  @Html.LabelFor(model => model.Address.StreetName)
Yini
la source
1
Il s'agit d'une excellente solution, et la validation du modèle fonctionne toujours sans scrupules. Merci!
AFM-Horizon
11

Dois-je faire une autre vue qui contient ces 2 vues?

Réponse: non

N'y a-t-il pas un autre moyen tel que (sans BigViewModel):

Oui , vous pouvez utiliser Tuple (apporte de la magie en ayant plusieurs modèles).

Code:

 @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)
     }
VCody
la source
Cela ne correspondrait-il pas correctement au contrôleur qui acceptait le formulaire? J'ai pensé qu'il chercherait "Article" dans votre cas avant de chercher "nom".
SolidSnake4444
7

Ajoutez ce ModelCollection.cs à vos modèles

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

Manette:

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 vue:

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
Morten Frederiksen
la source
J'aime cette approche car elle me permet d'utiliser différents modèles dans la même vue sans avoir besoin de les croiser.
Matt Small
6

un moyen simple de le faire

nous pouvons appeler tous les modèles en premier

@using project.Models

puis envoyez votre modèle avec viewbag

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

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

et en vue

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

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

puis utilisez facilement ce modèle

Pnsadeghy
la source
3

Mon conseil est de faire un modèle à grande vue:

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

Dans votre Index.cshtml, si par exemple vous avez 2 partiels:

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

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

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

et dans le contrôleur:

model=new BigViewModel();
model.LoginViewModel=new LoginViewModel();
model.RegisterViewModel=new RegisterViewModel(); 
alin
la source
2

Je veux dire que ma solution était comme la réponse fournie sur cette page stackoverflow: ASP.NET MVC 4, plusieurs modèles dans une vue?

Cependant, dans mon cas, la requête linq qu'ils ont utilisée dans leur contrôleur n'a pas fonctionné pour moi.

Ceci est dit requête:

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

Par conséquent, "à votre avis, spécifiez simplement que vous utilisez une collection de modèles de vue" n'a pas fonctionné pour moi non plus.

Cependant, une légère variation sur cette solution a fonctionné pour moi. Voici ma solution au cas où cela aiderait quelqu'un.

Voici mon modèle de vue dans lequel je sais que je n'aurai qu'une seule équipe mais cette équipe peut avoir plusieurs cartes (et j'ai un dossier ViewModels dans mon dossier Models btw, d'où l'espace de noms):

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

Maintenant, c'est mon contrôleur. C'est la différence la plus significative par rapport à la solution du lien référencé ci-dessus. Je construis le ViewModel pour envoyer à la vue différemment.

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

À mon avis, je ne le spécifie pas en tant que liste. Je fais juste "@model TaskBoard.Models.ViewModels.TeamBoards" Ensuite, j'ai seulement besoin d'un pour chacun lorsque j'itère sur les tableaux de l'équipe. Voici mon point de vue:

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

Je suis assez nouveau sur ASP.NET MVC, donc cela m'a pris un peu de temps pour comprendre cela. Donc, j'espère que ce message aidera quelqu'un à comprendre son projet dans un délai plus court. :-)

Notso
la source
2

vous pouvez toujours passer le deuxième objet dans un ViewBag ou View Data.

Nada N. Hantouli
la source
1
  1. Créez une nouvelle classe dans votre modèle et les propriétés de LoginViewModelet RegisterViewModel:

    public class UserDefinedModel() 
    {
        property a1 as LoginViewModel 
        property a2 as RegisterViewModel 
    }
  2. Utilisez ensuite UserDefinedModeldans votre vue.

Andrew
la source
1
oui, ça marche pour moi. puis je l'ai référencé comme ceci: (le modèle a été déclaré en haut de la vue. Il y avait 2 modèles à l'intérieur: profil et emailstuff...... @ Html.DisplayNameFor (model => model.profile.BlackoutBegin) In le contrôleur J'ai rempli l'un des modèles en utilisant @notso post ci-dessous. Je n'avais pas besoin de remplir l'autre parce que je l'utilisais juste pour une entrée.
JustJohn
0

Il s'agit d'un exemple simplifié avec IEnumerable.

J'utilisais deux modèles dans la vue: un formulaire avec des critères de recherche (modèle SearchParams) et une grille de résultats, et j'ai eu du mal à ajouter le modèle IEnumerable et l'autre modèle sur la même vue. Voici ce que j'ai trouvé, j'espère que cela aide quelqu'un:

@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; }
    }
}
l'amour en direct
la source