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)
 }
Shawn Mclean
źródło
3
zobacz to: codeproject.com/Articles/687061/…
S.Serpooshan
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

Omu
źródło
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.

TheRightChoyce
źródło
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.

Hamid Tavakoli
źródło
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)
Yini
źródło
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)
     }
VCody
źródło
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
Morten Frederiksen
źródło
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

Pnsadeghy
źródło
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(); 
alin
źródło
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. :-)

Notso
źródło
2

zawsze możesz przekazać drugi obiekt w ViewBag lub View Data.

Nada N. Hantouli
źródło
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.

Andrzej
źródło
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; }
    }
}
żywa miłość
źródło