Mehrere Modelle in einer Ansicht


302

Ich möchte 2 Modelle in einer Ansicht haben. Die Seite enthält sowohl LoginViewModelund RegisterViewModel.

z.B

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

Muss ich ein anderes ViewModel erstellen, das diese 2 ViewModels enthält?

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

Ich möchte, dass die Validierungsattribute in die Ansicht übernommen werden. Deshalb brauche ich die ViewModels.

Gibt es nicht einen anderen Weg wie (ohne 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, vielen Dank für den Link mit verschiedenen Optionen, nach 4 Jahren haben Sie einen Kommentar gepostet und es hat mir geholfen, ich habe gerade ViewBagfür jeden in der Ansicht verwendet, funktioniert
super

@stom Nur zu Ihrer Information: Der Postautor erhält immer eine Benachrichtigung, aber wenn Sie jemanden benachrichtigen möchten, müssen Sie ihn @wie hier vor seinen Namen setzen .
Jpaugh

Antworten:


260

Es gibt viele Möglichkeiten ...

  1. Mit Ihrem BigViewModel machen Sie:

    @model BigViewModel    
    @using(Html.BeginForm()) {
        @Html.EditorFor(o => o.LoginViewModel.Email)
        ...
    }
  2. Sie können 2 zusätzliche Ansichten erstellen

    Login.cshtml

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

    und register.cshtml dasselbe

    Nach der Erstellung müssen Sie sie in der Hauptansicht rendern und ihnen das Ansichtsmodell / die Ansichtsdaten übergeben

    so könnte es sein:

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

    oder

    @{Html.RenderPartial("login", Model.LoginViewModel)}
    @{Html.RenderPartial("register", Model.RegisterViewModel)}
  3. Die Verwendung von Ajax-Teilen Ihrer Website wird unabhängiger

  4. iframes, aber wahrscheinlich ist dies nicht der Fall


2
Ist es ein Problem, wenn 2 Textfelder aufgrund der Verwendung von Teilansichten denselben Namen im Formular haben?
Shawn Mclean

2
Nein, sollte fein sein - klicken Sie mit etwas wie Firebug (auf Firefox) auf das Element selbst und Sie sehen etwas wie id = "LoginViewModel_Email" name = "LoginViewModel.Email", also sind sie tatsächlich einzigartig! Ein Ansichtsmodell sollte das sein, was Sie brauchen. Posten Sie einfach jede Seite unter einer anderen URL und es sollte Ihnen gut gehen
Haroon

@Lol-Codierer Eigentlich wären es 2 Formulare, eines für jedes Ansichtsmodell, aber wenn Sie 2 oder 3 oder mehr mit demselben Namen hätten, würden Sie einfach ein Array mit diesem Namen auf der Serverseite erhalten (wenn Sie es in die Parameter einfügen der Post-Action-Methode)
Omu

@Chuck Norris Ich verwende asp.net mvc 4 und habe Ihre Partialviewresult-Technik implementiert, melde jedoch @Html.RenderActioneinen Fehler, dass Expression einen Wert zurückgeben muss
Deeptechtons

1
Können Sie erklären, wie das funktioniert? Ich verstehe, dass dies eine Lösung für das Problem ist, aber ich kann keinen Sinn daraus machen. Ich habe ein ähnliches (etwas anderes) Beispiel für dieses Problem und kann nicht herausfinden, wie ich es umgehen kann.
Andre

127

Ich würde empfehlen, Html.RenderActionPartialViewResults zu verwenden, um dies zu erreichen. Sie können dieselben Daten anzeigen, aber jede Teilansicht verfügt weiterhin über ein einzelnes Ansichtsmodell, sodass keine erforderlich istBigViewModel

Ihre Ansicht enthält also ungefähr Folgendes:

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

Wo Login& Registersind beide Aktionen in Ihrem Controller wie folgt definiert:

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

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

Das Login& Registerwäre dann ein Benutzersteuerelement, das sich entweder im aktuellen Ansichtsordner oder im freigegebenen Ordner befindet, und möchte Folgendes:

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

Und dort haben Sie eine einzelne Controller-Aktion, Ansicht und Ansichtsdatei für jede Aktion, wobei jede Aktion völlig unterschiedlich ist und für nichts voneinander abhängig ist.


4
Dies ist in Bezug auf das Design sehr sinnvoll, aber in Bezug auf die Effizienz müssen nicht drei vollständige Zyklen des MVC-Zyklus durchlaufen werden? stackoverflow.com/questions/719027/renderaction-renderpartial/…
Shawn Mclean

6
Ja, Sie haben Recht: Es verursacht jeweils einen zusätzlichen vollständigen MVC-Zyklus RenderAction. Ich vergesse immer seinen Teil des Futures-Pakets, da mein Projekt standardmäßig immer diese DLL enthält. Es hängt wirklich von den Präferenz- und Anwendungsanforderungen ab, ob die zusätzlichen MVC-Zyklen die Trennung wert sind, die sie auf der Entwurfsseite geben. Oft können Sie die RenderActionErgebnisse zwischenspeichern, sodass der einzige Treffer, den Sie erzielen, die geringfügige zusätzliche Verarbeitung über die Controller-Factory ist.
TheRightChoyce

Ich habe das oben genannte implementiert .. was fehlt mir? Bitte helfen Sie: stackoverflow.com/questions/9677818/…
diegohb

Heiliger Strohsack! Das hat bei mir von Anfang an perfekt funktioniert. Ich baue eine interne Site mit nur ein paar Benutzern auf ... Effizienz ist also kein wirkliches Anliegen von mir. VIELEN DANK!
Derek Evermore

1
Musste geschweifte Klammern verwenden, um PartialView zum Laufen zu bringen.
null

113

Ein anderer Weg ist zu verwenden:

@model Tuple<LoginViewModel,RegisterViewModel>

Ich habe für ein anderes Beispiel erklärt, wie diese Methode sowohl in der Ansicht als auch im Controller verwendet wird: Zwei Modelle in einer Ansicht in ASP MVC 3

In Ihrem Fall können Sie es mit dem folgenden Code implementieren:

In der Ansicht:

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

Beachten Sie, dass ich die Namensattribute für jede Eigenschaft beim Erstellen des Formulars manuell geändert habe. Dies muss durchgeführt werden, da es sonst nicht ordnungsgemäß dem Parameter des Methodenmodells der Methode zugeordnet wird, wenn Werte zur Verarbeitung an die zugehörige Methode gesendet werden. Ich würde vorschlagen, separate Methoden zu verwenden, um diese Formulare separat zu verarbeiten. In diesem Beispiel habe ich die Methoden Login1 und Login2 verwendet. Für die Login1-Methode muss ein Parameter vom Typ RegisterViewModel und für Login2 ein Parameter vom Typ LoginViewModel erforderlich sein.

Wenn ein Aktionslink erforderlich ist, können Sie Folgendes verwenden:

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

In der Methode des Controllers für die Ansicht muss eine Variable vom Typ Tuple erstellt und dann an die Ansicht übergeben werden.

Beispiel:

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

Oder Sie können die beiden Instanzen von LoginViewModel und RegisterViewModel mit Werten füllen und diese dann an die Ansicht übergeben.


Dies war eine großartige Möglichkeit, damit umzugehen, danke! Habe was ich brauchte.
Todd Davis

Ich habe es versucht, aber wenn ich EditorForoder HiddenFor(was idealerweise das ist, was ich verwenden möchte) verwende, werden die Modelleigenschaften nicht festgelegt, wenn die Login1/ Login2controller-Methoden aufgerufen werden. Vermutlich wird das @Name=Mapping ignoriert. Benötigt man HiddenForfür diese Situation einen anderen Trick?
Gary Chapman

1
Dies wird überhaupt nicht funktionieren - Sie können nicht an das Modell binden, wenn das Formular

@ Hamid Danke Hamid, für einen Neuling in MVC war dies die einfachste Antwort für mich. Danke dir.
Harold_Finch

Wie haben Sie das Modell beim Absenden des Formulars gebunden?
Mohammed Noureldin

28

Verwenden Sie ein Ansichtsmodell, das mehrere Ansichtsmodelle enthält:

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

Aus Ihrer Sicht:

  @model MyProject.Web.ViewModels.UserViewModel

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

1
Dies ist eine großartige Lösung, und die Modellvalidierung funktioniert immer noch ohne Bedenken. Vielen Dank!
AFM-Horizon

11

Muss ich eine andere Ansicht erstellen, die diese beiden Ansichten enthält?

Antwort: Nein

Gibt es nicht einen anderen Weg wie (ohne das BigViewModel):

Ja , Sie können Tupel verwenden (bringt Magie in Sicht, wenn Sie mehrere Modelle haben).

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

Wäre dies nicht richtig auf den Controller abgebildet, der das Formular akzeptiert hat? Ich dachte, es würde in Ihrem Fall nach "Item" suchen, bevor nach "name" gesucht wird.
SolidSnake4444

7

Fügen Sie diese ModelCollection.cs Ihren Modellen hinzu

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

Regler:

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

Die Aussicht:

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

Ich mag diesen Ansatz, da ich damit verschiedene Modelle in derselben Ansicht verwenden kann, ohne dass sie sich überschneiden müssen.
Matt Small

6

ein einfacher Weg, das zu tun

Wir können alle Modelle zuerst aufrufen

@using project.Models

Dann senden Sie Ihr Modell mit Viewbag

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

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

und im Blick

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

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

Verwenden Sie dann einfach dieses Modell


3

Mein Rat ist, ein großes Ansichtsmodell zu erstellen:

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

Wenn Sie in Ihrer Index.cshtml beispielsweise zwei Teiltöne haben:

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

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

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

und im Controller:

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

2

Ich möchte sagen, dass meine Lösung der Antwort auf dieser Stapelüberlaufseite entsprach: ASP.NET MVC 4, mehrere Modelle in einer Ansicht?

In meinem Fall funktionierte die in ihrem Controller verwendete Linq-Abfrage jedoch nicht für mich.

Dies ist die Abfrage:

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

Folglich hat "in Ihrer Ansicht nur angeben, dass Sie eine Sammlung von Ansichtsmodellen verwenden" auch bei mir nicht funktioniert.

Eine geringfügige Abweichung von dieser Lösung hat bei mir jedoch funktioniert. Hier ist meine Lösung für den Fall, dass dies jemandem hilft.

Hier ist mein Ansichtsmodell, in dem ich weiß, dass ich nur ein Team haben werde, aber dieses Team möglicherweise mehrere Boards hat (und ich habe übrigens einen ViewModels-Ordner in meinem Models-Ordner, daher der Namespace):

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

Das ist mein Controller. Dies ist der bedeutendste Unterschied zur Lösung in dem oben genannten Link. Ich baue das ViewModel aus, um es anders an die Ansicht zu senden.

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

Dann spezifiziere ich es meiner Ansicht nach nicht als Liste. Ich mache einfach "@model TaskBoard.Models.ViewModels.TeamBoards". Dann brauche ich nur eine für jede, wenn ich über die Boards des Teams iteriere. Hier ist meine Ansicht:

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

Ich bin ziemlich neu in ASP.NET MVC, daher habe ich eine Weile gebraucht, um dies herauszufinden. Ich hoffe, dieser Beitrag hilft jemandem, es in kürzerer Zeit für sein Projekt herauszufinden. :-)



1
  1. Erstellen Sie eine neue Klasse in Ihrem Modell und den Eigenschaften von LoginViewModelund RegisterViewModel:

    public class UserDefinedModel() 
    {
        property a1 as LoginViewModel 
        property a2 as RegisterViewModel 
    }
  2. Dann verwenden Sie UserDefinedModelin Ihrer Ansicht.


1
Ja, das funktioniert bei mir. dann habe ich es so referenziert: (Modell wurde oben in der Ansicht deklariert. Es befanden sich 2 Modelle darin: Profil und E-Mail-Material ... @ Html.DisplayNameFor (model => model.profile.BlackoutBegin) In Der Controller Ich habe eines der Modelle mit @notso Post unten gefüllt. Ich musste das andere nicht füllen, weil ich es nur für eine Eingabe verwendet habe.
JustJohn

0

Dies ist ein vereinfachtes Beispiel mit IEnumerable.

Ich habe zwei Modelle in der Ansicht verwendet: ein Formular mit Suchkriterien (SearchParams-Modell) und ein Raster für Ergebnisse, und ich hatte Probleme damit, das IEnumerable-Modell und das andere Modell in derselben Ansicht hinzuzufügen. Folgendes habe ich mir ausgedacht, hoffe, das hilft jemandem:

@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; }
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.