Wiele modeli w widoku


302

Chcę mieć 2 modele w jednym widoku. Strona zawiera zarówno LoginViewModeli RegisterViewModel.

na przykład

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

Czy muszę utworzyć kolejny ViewModel, który będzie zawierał te 2 modele ViewModel?

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

Potrzebuję atrybutów sprawdzania poprawności, aby przejść do widoku. Właśnie dlatego potrzebuję ViewModels.

Czy nie ma innego sposobu, jak (bez 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, bardzo dziękuję za link z różnymi opcjami, po 4 latach opublikowałeś komentarz i pomógł mi, po prostu użyłem ViewBagdla każdego w widoku, działa świetnie
— shaijut

@stom Tylko FYI: autor postu zawsze otrzymuje powiadomienie, ale jeśli chcesz powiadomić kogoś innego, musisz @podać jego nazwisko, tak jak ja tutaj.
— jpaugh

Odpowiedzi:


260

Istnieje wiele sposobów ...

  1. z BigViewModel robisz:

    @model BigViewModel    
    @using(Html.BeginForm()) {
        @Html.EditorFor(o => o.LoginViewModel.Email)
        ...
    }
  2. możesz utworzyć 2 dodatkowe widoki

    Login.cshtml

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

    i register.cshtml to samo

    po utworzeniu musisz wyrenderować je w widoku głównym i przekazać im viewmodel / viewdata

    więc może być tak:

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

    lub

    @{Html.RenderPartial("login", Model.LoginViewModel)}
    @{Html.RenderPartial("register", Model.RegisterViewModel)}
  3. korzystanie z części ajax Twojej witryny staje się bardziej niezależne

  4. iframes, ale prawdopodobnie tak nie jest


2
Czy problem polega na tym, że 2 pola tekstowe mają taką samą nazwę w formularzu z powodu korzystania z częściowych widoków?
— Shawn Mclean

2
Nie, powinno być w porządku - kliknij sam element za pomocą czegoś takiego jak firebug (na Firefox), a zobaczysz coś takiego jak id = „LoginViewModel_Email” name = „LoginViewModel.Email”, więc w rzeczywistości są unikalne! Model widoku powinien być tym, czego potrzebujesz, po prostu opublikuj każdą stronę pod innym adresem URL i wszystko powinno być w porządku
— Haroon

Koder @ Lol faktycznie miałby 2 formularze, po jednym dla każdego viewmodelu, ale w każdym razie, jeśli miałbyś 2 lub 3 lub więcej o tej samej nazwie, po prostu dostaniesz tablicę o tej nazwie po stronie serwera (jeśli umieścisz ją w params metody post action)
— Omu

@Chuck Norris Używam asp.net mvc 4 i zaimplementowałem technikę częściowego podglądu, ale @Html.RenderActionzgłasza błąd, że wyrażenie musi zwrócić wartość
— Deeptechtons

1
Czy możesz wyjaśnić, jak to działa? Rozumiem, że to rozwiązanie problemu, ale nie mogę tego zrozumieć. Mam podobny (nieco inny) przykład tego problemu i nie mogę wymyślić, jak go obejść.
— Andre

127

W tym celu zaleciłbym użycie Html.RenderActioni PartialViewResults; pozwoli ci wyświetlać te same dane, ale każdy widok częściowy nadal będzie miał jeden model widoku i eliminuje potrzebę korzystania z niegoBigViewModel

Twój widok zawiera więc coś takiego:

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

Gdzie Logini Registersą obie akcje w twoim kontrolerze zdefiniowane w następujący sposób:

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

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

Wówczas Login& Registerbyłyby elementami sterującymi użytkownika znajdującymi się w bieżącym folderze Widok lub w folderze współdzielonym i chcieliby coś takiego:

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

I tam masz jedną akcję kontrolera, przeglądaj i przeglądaj plik dla każdej akcji z każdą całkowicie odrębną i nie zależną od siebie w niczym.


4
Ma to sens w zakresie projektowania, ale jeśli chodzi o wydajność, czy nie musi on przechodzić przez 3 pełne cykle cyklu MVC? stackoverflow.com/questions/719027/renderaction-renderpartial/…
— Shawn Mclean

6
Tak, masz rację: powoduje to dodatkowy pełny cykl MVC dla każdego RenderAction. Zawsze zapominam o jego części pakietu futures, ponieważ mój projekt zawsze domyślnie zawiera tę bibliotekę DLL. To naprawdę zależy od preferencji i wymagań aplikacyjnych, czy dodatkowe cykle mvc są warte rozdzielenia, które daje po stronie projektowania. Wiele razy możesz buforować RenderActionwyniki, więc jedynym trafieniem jest niewielkie dodatkowe przetwarzanie przez fabrykę kontrolera.
— TheRightChoyce

Zaimplementowałem powyższe ... czego mi brakuje? Proszę o pomoc: stackoverflow.com/questions/9677818/…
— diegohb

O kurczę! To zadziałało dla mnie idealnie od samego początku. Buduję wewnętrzną witrynę z tylko kilkoma użytkownikami ... więc wydajność nie jest moim głównym zmartwieniem. DZIĘKUJĘ CI!
— Derek Evermore

1
Musiałem użyć nawiasów klamrowych, aby PartialView działał.
— zerowy

113

Innym sposobem jest użycie:

@model Tuple<LoginViewModel,RegisterViewModel>

Wyjaśniłem, jak używać tej metody zarówno w widoku, jak i kontrolerze na inny przykład: Dwa modele w jednym widoku w ASP MVC 3

W twoim przypadku możesz go zaimplementować za pomocą następującego kodu:

W widoku:

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

Zauważ , że ręcznie zmieniłem atrybuty Nazwa dla każdej właściwości podczas budowania formularza. Trzeba to zrobić, w przeciwnym razie nie zostałby poprawnie odwzorowany na parametr metody typu modelu, gdy wartości są wysyłane do powiązanej metody przetwarzania. Sugerowałbym użycie osobnych metod do osobnego przetwarzania tych formularzy, w tym przykładzie użyłem metod Login1 i Login2. Metoda Login1 wymaga parametru typu RegisterViewModel, a Login2 wymaga parametru typu LoginViewModel.

jeśli wymagany jest link akcji, możesz użyć:

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

w metodzie kontrolera dla widoku należy utworzyć zmienną typu Tuple, a następnie przekazać ją do widoku.

Przykład:

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

lub możesz wypełnić dwa wystąpienia LoginViewModel i RegisterViewModel wartościami, a następnie przekazać je do widoku.


To był świetny sposób, aby sobie z tym poradzić, dzięki! Zrobiłem to, czego potrzebowałem.
— Todd Davis

Próbowałem tego, ale jeśli używam EditorForlub HiddenFor(co jest dokładnie tym, czego chcę użyć), właściwości modelu nie są ustawione, gdy wywoływane są metody Login1/ Login2controller. Przypuszczalnie @Name=mapowanie jest ignorowane. Czy HiddenForwymaga innej sztuczki w tej sytuacji?
— Gary Chapman,

1
To w ogóle nie zadziała - nie można powiązać z modelem po przesłaniu formularza

@Hamid Dzięki Hamid, dla nowicjusza w MVC, była to dla mnie najbardziej uproszczona odpowiedź. Dziękuję Ci.
— Harold_Finch

Jak związałeś model podczas przesyłania formularza?
— Mohammed Noureldin

28

Użyj modelu widoku zawierającego wiele modeli widoku:

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

Twoim zdaniem:

  @model MyProject.Web.ViewModels.UserViewModel

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

1
To świetne rozwiązanie, a sprawdzanie poprawności modelu nadal działa bez żadnych wątpliwości. Dzięki!
— AFM-Horizon

11

Czy muszę utworzyć inny widok zawierający te 2 widoki?

Odpowiedź: Nie

Czy nie ma innego sposobu, takiego jak (bez BigViewModel):

Tak , możesz użyć Tuple (pokazuje magię, mając wiele modeli).

Kod:

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

Czy to nie byłoby poprawnie mapowane na kontroler, który akceptuje formularz? Myślałem, że będzie szukał „przedmiotu” w twoim przypadku, zanim zacznie szukać „imienia”.
— SolidSnake4444

7

Dodaj ten ModelCollection.cs do swoich modeli

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

Kontroler:

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

Widok:

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

Podoba mi się to podejście, ponieważ pozwala mi korzystać z różnych modeli w tym samym widoku bez konieczności ich krzyżowania się.
— Matt Small,

6

prosty sposób na zrobienie tego

najpierw możemy zadzwonić do wszystkich modeli

@using project.Models

następnie wyślij swój model z viewbag

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

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

i na widoku

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

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

następnie łatwo użyj tego jak Model


3

Radzę zrobić model z dużym widokiem:

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

W pliku Index.cshtml, jeśli na przykład masz 2 częściowe:

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

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

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

i w kontrolerze:

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

2

Chcę powiedzieć, że moje rozwiązanie było jak odpowiedź podana na tej stronie przepływu stosu : ASP.NET MVC 4, wiele modeli w jednym widoku?

Jednak w moim przypadku zapytanie linq użyte w kontrolerze nie działało dla mnie.

To jest powiedziane zapytanie:

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

W związku z tym „w twoim widoku po prostu określ, że korzystasz z kolekcji modeli widoków” również dla mnie nie działało.

Jednak niewielka zmiana tego rozwiązania zadziałała dla mnie. Oto moje rozwiązanie na wypadek, gdyby komukolwiek to pomogło.

Oto mój model widoku, w którym wiem, że będę miał tylko jeden zespół, ale ten zespół może mieć wiele tablic (i mam folder ViewModels w moim folderze Modele btw, stąd przestrzeń nazw):

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

To jest mój kontroler. Jest to najbardziej znacząca różnica w stosunku do rozwiązania w linku, o którym mowa powyżej. Buduję ViewModel, aby wysyłać do widoku inaczej.

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

Moim zdaniem nie określam go jako listy. Po prostu wykonuję „@model TaskBoard.Models.ViewModels.TeamBoards”. Potrzebuję tylko dla każdego, gdy iteruję po deskach zespołu. Oto mój pogląd:

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

Jestem dość nowy w ASP.NET MVC, więc zrozumienie tego zajęło mi trochę czasu. Mam więc nadzieję, że ten post pomoże komuś w znalezieniu projektu w krótszym czasie. :-)



1
  1. Stwórz jedną nową klasę w modelu i właściwości LoginViewModeloraz RegisterViewModel:

    public class UserDefinedModel() 
    {
        property a1 as LoginViewModel 
        property a2 as RegisterViewModel 
    }
  2. Następnie użyj UserDefinedModelw swoim widoku.


1
tak, to działa dla mnie. następnie odniosłem się do tego w następujący sposób: (model został zadeklarowany w górnej części widoku. W środku były 2 modele: profil i wiadomość e-mail...... @ Html.DisplayNameFor (model => model.profile.BlackoutBegin) W kontroler Wypełniłem jeden z modeli za pomocą poniższego postu @notso. Nie musiałem wypełniać drugiego, ponieważ po prostu użyłem go do wprowadzania danych
— JustJohn

0

To jest uproszczony przykład z IEnumerable.

Korzystałem z dwóch modeli w widoku: formularza z kryteriami wyszukiwania (model SearchParams) i siatki wyników, i miałem problem z dodaniem modelu IEnumerable i drugiego modelu do tego samego widoku. Oto, co wymyśliłem, mam nadzieję, że to komuś pomoże:

@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; }
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.