ASP.NET MVC - How exactly to use View Models
Asked Answered
F

3

38

Let's say I have a page that allows the editing of a user's details, so I have a ViewModel like this:

public class UserViewModel {
    public string Username { get; set; }
    public string Password { get; set; }
    public int ManagerId { get; set; }
    public string Category { get; set; }
}

So on my EditUser action I can have this passed back by the model binder and then I can map that to the Domain Model:

public ActionResult EditUser(UserViewModel user) {
    ...

However, the page that displays the form also needs details such as a list of Managers and Categories to provide dropdowns for those fields. It might also display a list of other users in a sidebar so you can switch between the different users you're editing.

So then I have another view model:

public class ViewUserViewModel {
    public UserViewModel EditingUser { get; set; }
    public IEnumerable<SelectListItem> Managers { get; set; }
    public IEnumerable<SelectListItem> Categories { get; set; }
    public IEnumerable<SelectListItem> AllUsers { get; set; }
}

Is this the correct way to do it? Are they both View Models? If so, is there a naming convention I should use so I can distinguish between VMs that are like models and VMs that just contain data for the page?

Have I got this all wrong?

Filter answered 14/5, 2013 at 16:32 Comment(0)
F
20

How I do this in shortcut:

  1. Create separate ViewModel class for each form on the page, then I render these classes with PartialViews as @{Html.RenderPartial("PartialName", Model.PartialModel);}.
  2. If page contains things like html metas I make separated class for metas and put it in section on the page.
  3. Rest cases like "should I put this in separated class?" is your judgement.

So for example you have page which has some kind of login/register bar or popup whatever.

public class SomePageViewModel
{
    public RegisterBarVM Register { get; set; }
    public LoginBarVM LoginBar { get; set; }

    public MetasVM Metas { get; set; }
    public string MaybePageTitle { get; set;}
    public string MaybePageContent { get; set;}

    [HiddenInput(DisplayValue = false)]
    public int IdIfNeeded { get; set; }

    public IEnumerable<SelectListItem> SomeItems {get; set;}
    public string PickedItemId { get;set; }
}

public class RegisterBarVM
{
    public string RegisterUsername {get;set;}
    public string RegisterPassword {get;set;}
    //...
}

public class LoginBarVM
{
    public string LoginUserame {get;set;}
    public string LoginPassword {get;set;}
    //...
}

//cshtml
@model yourClassesNamespace.SomePageViewModel
@{
    Html.RenderPartial("LoginBar", Model.LoginBar); //form inside
    Html.RenderPartial("RegisterBar", Model.RegisterBar); //form inside

    using(Html.BeginForm())
    {
        @Html.EditorFor(m => m.IdIfNeeded)
        @Hmtl.EditorFor(m => m.MaybePageTitle)
        @Hmtl.EditorFor(m => m.MaybePageContent)

        @Hmtl.DropDownListFor(m => m.PickedItemId, new SelectList(Model.SomeItems))

        <input type="submit" value="Update" />
    }
}

@section Metas {
    @{Html.RenderPartial("Meatas", Model.Metas}
}

About editor templates Brad Wilsons Blog and just google or look for stacks resources about display/editor templates and HtmlHelpers. They all are very useful for building consistent websites.

Fives answered 14/5, 2013 at 21:20 Comment(0)
O
95

"View Model" is just a pattern. There's nothing magical about the name, but generally any class being passed to a view (whether for simply displaying data or for the purposes of form submissions) is referred to as a "view model" and given a name like FooViewModel or FooVM to indicate that it's part of that "view model" pattern.

I don't want to go too philosophical on you, but I think a little bit of reference about the patterns in play will be helpful. ASP.NET MVC obviously enough encourages an MVC (Model-View-Controller) architectural model. In MVC the Model is the container for all the application's business logic. The Controller is responsible for handling the request, fetching the model, rendering the View with that model and returning a response. That seems like a lot of responsibility but in actuality the framework handles most of this behind the scenes, so Controllers are typically (and should be) very light on code. They are responsible for the bare minimum amount of functionality to wire everything up. Finally, the View is responsible for creating the UI layer that allows the user to interact with the data in the Model. It is not responsible for the data itself, nor should it be (ViewData/ViewBag is a pretty big violation here, at least in as much as the way it ends up being used by developers in practice).

So, that means the bulk of your application logic should be in your model, and typically that's a good thing. However, since the model is the haven of application data, it generally gets persisted in a database or similar. That creates some conflict of interest as you now need to start a balancing act between what data should be persisted and what data should only exist for the purpose of display.

This is where view models come in. MVVM (Model-View-View Model), a somewhat parallel pattern to MVC, recognizes the inherent issues in a one-model-to-rule-them-all approach. I won't go into much detail here, because MVC doesn't use this pattern. However, most ASP.NET MVC developers have co-opted the View Model of MVVM. What you essentially end up with is a database-backed entity (the traditional model) and then usually many different view models that represent that entity in various states. This allows your model to contain the business logic that's relevant to persistence while the view model(s) contain the business logic relevant to displaying, creating and updating that model.

I've gone off track a little, but the long and short is that what you're doing is perfectly acceptable. In fact, it's good practice. Create as many view models as your application requires, and use them to actually store the data and business logic necessary for your views. (That includes things like SelectLists. Neither your controller nor view should need to know how to create a SelectList for a dropdown.)

Onslaught answered 14/5, 2013 at 18:28 Comment(3)
The best explanation about this subject. You should put it on a Blog! +1Reparative
You have mentioned the models should be responsible for the application's business logic. By business you probably mean all the data preparation, querying, filtering, projection one model to another, or a particular ViewModel. And then such prepared ViewModel is passed to View by controller. How do you physically do it? How do you design models in order to do the business? Do you for instance move all controllers methods to classes representing view models? Currently, I have a lot of functions and "business" in controllers which do all the bits and bolts. thanksLacrosse
Frankly, you can't really do it in ASP.NET MVC. To really understand what I'm talking about, build a sample app in Ruby on Rails. RoR adheres to the MVC pattern very strictly, and you'll see exactly how much goes into their models. ASP.NET MVC, on the other hand only loosely adheres to MVC. You "Model" will be some combination of entity classes, view models, and something like a repository or service. You should try to still keep your controllers thin, you just can't move all the logic into one class.Onslaught
F
20

How I do this in shortcut:

  1. Create separate ViewModel class for each form on the page, then I render these classes with PartialViews as @{Html.RenderPartial("PartialName", Model.PartialModel);}.
  2. If page contains things like html metas I make separated class for metas and put it in section on the page.
  3. Rest cases like "should I put this in separated class?" is your judgement.

So for example you have page which has some kind of login/register bar or popup whatever.

public class SomePageViewModel
{
    public RegisterBarVM Register { get; set; }
    public LoginBarVM LoginBar { get; set; }

    public MetasVM Metas { get; set; }
    public string MaybePageTitle { get; set;}
    public string MaybePageContent { get; set;}

    [HiddenInput(DisplayValue = false)]
    public int IdIfNeeded { get; set; }

    public IEnumerable<SelectListItem> SomeItems {get; set;}
    public string PickedItemId { get;set; }
}

public class RegisterBarVM
{
    public string RegisterUsername {get;set;}
    public string RegisterPassword {get;set;}
    //...
}

public class LoginBarVM
{
    public string LoginUserame {get;set;}
    public string LoginPassword {get;set;}
    //...
}

//cshtml
@model yourClassesNamespace.SomePageViewModel
@{
    Html.RenderPartial("LoginBar", Model.LoginBar); //form inside
    Html.RenderPartial("RegisterBar", Model.RegisterBar); //form inside

    using(Html.BeginForm())
    {
        @Html.EditorFor(m => m.IdIfNeeded)
        @Hmtl.EditorFor(m => m.MaybePageTitle)
        @Hmtl.EditorFor(m => m.MaybePageContent)

        @Hmtl.DropDownListFor(m => m.PickedItemId, new SelectList(Model.SomeItems))

        <input type="submit" value="Update" />
    }
}

@section Metas {
    @{Html.RenderPartial("Meatas", Model.Metas}
}

About editor templates Brad Wilsons Blog and just google or look for stacks resources about display/editor templates and HtmlHelpers. They all are very useful for building consistent websites.

Fives answered 14/5, 2013 at 21:20 Comment(0)
S
9

I personally prefer to put all the information required for the page to render in the ViewModel, as that is the purpose of the ViewModel - to provide all of the data for the View. So my UserViewModel would contain properties for Managers, Categories and AllUsers and the controller would fill those collections prior to passing the ViewModel off to the view.

This is essentially what you have done - it just removes the extra ViewModel from the equation.

I have also seen other programmers use the ViewData to send the dropdown lists to the view, but I dislike that because ViewData is not strongly typed, whereas a ViewModel is.

Soccer answered 14/5, 2013 at 16:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.