View Model Design And Use In Razor Views

The recommended practice for transferring data to and from views in ASP.NET MVC is to use a View Model. But what is a View Model? How to you design one and how should they be used? This article attempts to provide answers to those questions.

In the olden days of web development, developers used RecordSet objects or more recently DataSets or DataTables as a means to transfer data from the database to a view template. The View template might have been a classic ASP file containing a mix of HTML and server-side code, or a Web Form consisting of databound server controls like a GridView or ListView. Regardless, the data is untyped and working with it usually consists of referring to items by index or by using "magic strings" to reference data container values that borrow from the schema of the database that the data originated from.

The MVC framework introduced the idea of strongly-typed views to ASP.NET, primarily to provide some Intellisense support to working with data in Views, compile-time type checking, remove the need to cast objects to other types in the View and to provide the ability to scaffold Views from the Model that the View derives from. Developers can still pass "untyped" data to Views using the ViewBag (or ViewData) mechanism. This is the quickest way to throw arbitrary values at a View. In fact, the default MVC 3 templated Home controller shows how to pass the string ""Welcome to ASP.NET MVC!" to the Index View using ViewBag.

Strongly-typed Views feature the @model directive at the top of a Razor ViewEngine file, which specifies the actual type that the View derives from:

@model ViewModelSample.Models.MyModel

This is added automatically when you use the View creation dialogue and select the option to make the View strongly-typed:

The Model class dropdown will become enabled, and will list all classes available to your project. In this particular example, I have added an ADO.NET Entity Data Model derived from the Northwind sample database to the MVC application being used for illustrative purposes. You can see the familiar auto-generated Product, Customer and Category classes in the dropdown list above.

When the View is compiled, a C# class is created:

public class _Page_Views_Home_Index_cshtml : System.Web.Mvc.WebViewPage<ViewModelSample.Models.MyModel>

The name of the class is derived from the name and location of the View file, and inherits from WebViewPage<TModel>, and it is this that provides the strong-typing and IntelliSense support etc. If no View Model type is set in a model directive, the type that is used instead is dynamic:

public class _Page_Views_Home_Index_cshtml : System.Web.Mvc.WebViewPage<dynamic>
What goes into the View Model?

This is the question that seems to be asked most often. So far as the Add View dialogue is concerned any class in the correct location is a candidate for a strongly-typed View. The collection of classes that were generated by the Entity Framework from the Northwind database are usually known as Domain Entities. It is not unusual to find Views deriving directly from these entities in tutorials and samples. One of the main reasons for this is that it is a quick route to generating demo-code. And sometimes it might even be appropriate where the system being developed is one that largely provides a CRUD application over those entities. If you want to create or update a Category in Northwind, all you really need is a CategoryName and Description property.

Data Annotation attributes are used to manage model validation at property level, as well as display labels and some aspects of scafoolding views. If the model class code is generated automatically, such as with the entity Framework, the file defining the domain entities is regenerated whenever the database is changed. You can use "buddy" or partial classes to apply attributes to domain entity properties. Here's a buddy class for the Category class:

[MetadataType(typeof(CategoryMetadata))]
public partial class Category
{
}

It is empty, but has its own attribute - the MetadataType attribute which associates the source of the metadata to be applied to the Category class. In this case, the attribute points to a type called CategoryMetadata whose definition is as follows:

public class CategoryMetadata
{
    [HiddenInput(DisplayValue = true)]
    public int CategoryId { get; set; }

    [Required, Display(Name = "Category Name")]
    public string CategoryName { get; set; }
}

Regardless how many times the edmx file is regenerated, the metadata attributes are safe from harm.

The real world, however, is not often as straightforward. Usually, Views are complex and include artifacts from more than one domain entity. And perhaps only a subset of any entity's properties. The solution is to create a class whose sole role is to act as a container for a specific View's data. Or a Model for the View, if you will, or a View Model. A common approach to producing a View Model is to compose it from some domain entities and perhaps a sprinkling of properties. A View for adding a new product to the Northwind database will need fields for all of the Product properties together with a way of specifying which Category the new Product object belongs to. Here's something that will do the job:

public class CreateProductViewModel
{

    public Product Product { get; set; }

    [Required, Display(Name = "Select a category")]
    public int CategoryId { get; set; }
    public SelectList Categories { get; set; }
    [Required, Display(Name = "Select a supplier")]
    public int SupplierId { get; set; }
    public SelectList Suppliers { get; set; }
}

The Product object comes directly from the domain entities generated by the Entity Framework. It will benefit from any validation etc attributes that may have been applied to a buddy class. The advantage of this approach is that code is reused in a DRY way, and the Product property needs little to no work once validated to prepare it for persistence by the data access layer.

Mappers

There is a school of thought that domain entities are not the place for setting validation rules or scaffolding and labelling instructions, because these are are purely presentational concerns. Therefore the entity should not be exposed to the presentation layer, even as part of a composite View Model class. There are also security concerns related to mass-assignment vulnerabilities and over-posting attacks where malicious users can craft HTTP requests that include values for entity properties that are not included in the HTML form. The default model binding within MVC will cause those values to be updated or added along with legitimate fields. A built-from-the-ground up View Model solves both these concerns. Rather than include a domain entity (and all of its properties), you only include properties that are required for the specific View. Taking this approach, the CreateProductViewModel will look slightly different:

public class CreateProductViewModel
{
    [Required, Display(Name = "Product Name"), MaxLength(100)]
    public string ProductName { get; set; }
    [Required, Display(Name = "Quantity Per Unit"), MaxLength(20)]
    public string QuantityPerUnit { get; set; }
    [Required, Display(Name = "Price per unit")]
    public double UnitPrice { get; set; }
    [Required, Display(Name = "Reorder Level")]
    public int ReorderLevel { get; set; }
    [Required, Display(Name = "Select a category")]
    public int CategoryId { get; set; }
    public SelectList Categories { get; set; }
    [Required, Display(Name = "Select a supplier")]
    public int SupplierId { get; set; }
    public SelectList Suppliers { get; set; }
}

Only those properties that need to be are exposed through the highly customised Vew Model. The Product class has a Discontinued property - a bool. This highly customised View Model doesn't include that property so any attempt to set the Discontinued value by over posting using the revised View Model will not work - so long as the parameter to the Controller Action accepts the View Model and not a Product object. So this approach helps to ensure separation of concerns and offers some additional security, but it means that the values posted to the controller need to be mapped to an entity to be persisted. The data layer deals with Product objects, not View Models. For fairly simple objects, that should be too much trouble:

[HttpPost]
public ActionResult Create(CreateProductViewModel model)
{
    if(ModelState.IsValid) {
        var product = new Product {
                          ProductName = model.ProductName,
                          CategoryID = model.CategoryId,
                          SupplierID = model.SupplierId,
                          QuantityPerUnit = model.QuantityPerUnit
                          //etc
                                  };
        _repository.Save(product);
        return RedirectToAction("Index");
    }
    return View(model);
}

However, mapping View Models to entities is made a lot simpler using a tool - AutoMapper, which is described as a convention-based object mapper. In other words, it maps one object (the View Model) to another (the Entity) based on the convention of properties on each object being named the same. It's actually more flexible than that - you can tell it to map mismatched properties too. AutoMapper is available from Nuget. You can access it within Visual Studio by going to Tools > Library Package Manager > Package Manager Console, then typing

PM> Install-Package AutoMapper

Once it has installed successfully, you simple create a mapping between two objects, then sit back:

[HttpPost]
public ActionResult Create(CreateProductViewModel model)
{
    if(ModelState.IsValid) {
        var product = Mapper.Map<CreateProductViewModel, Product>(model);
        _repository.Save(product);
        return RedirectToAction("Index");
    }
    return View(model);
}

My preference is to generate View Models specific for particular Views. While this may involve extra coding - and some might say a duplication of properties across entities and View Models, AutoMapper helps to minimise the additional work involved.

 

Date Posted: Saturday, May 26, 2012 11:21 PM
Last Updated: Friday, June 1, 2012 7:21 AM
Posted by: Mikesdotnetting
Total Views to date: 58535

9 Comments

Wednesday, May 30, 2012 9:27 PM - Zeljko

Shouldn't the line:
Mapper.CreateMap();
be called outside of the controller, at startup of the application?

Thursday, May 31, 2012 5:28 AM - Mike

@Zeljko,

Yes, you are right - thanks. The code here is example only, but I will amend things to make that clearer.

Thursday, May 31, 2012 6:38 AM - Zeljko

And something else regarding Automapper... Though I use it like you to map the viewmodel to the model, the author of Automapper doesn't think it's appropriate. He says Automapper was designed to 'flatten' the model (model to viewmodel conversion) but not the way round.

Sunday, June 10, 2012 3:59 PM - Jules Bartow

Mike,

You have the most cogent intro to ViewModels anywhere. Thank you.

However, inserts are easy. Most introductions seem to avoid the more difficult updates like the plague.

I'm looking for the next step in your excellent explanation with a similar walkthrough of using Lambda selection of joined tables between complex types with updates where a ViewModel represents subsets of multiple domain entities including Table per Type (TPT) inheritance with 1:0..1 relationships and the corresponding attaches and entry state changes using DbContext in Razor MVC pages.

Can you hold our hands (virtually) and coach us through the rest of the basics? Please.

I want to make ancestor errors and foreign key constraint violations a thing of the past.

Thursday, June 14, 2012 9:56 PM - Mike

@Jules,

That's more of an Entity Framework topic than an ASP.NET one.

Monday, October 22, 2012 2:30 PM - Zipper

Great article Mike.

Any chance you can show how to retrieve the data as well as update it? I have yet to find a good example of all three scenarios on a semi-complex ViewModel (like yours which is perfect by the way in terms).

Thanks,
Zip

Sunday, March 3, 2013 5:05 PM - Andi

I would like some property inheritance property. Something like that in your ViewModel:

[CopyMetadataFromSamePropertyName(typeof(Product))]
public string ProductName { get; set; }

Because i feel, that no one can have the properties in sync

Wednesday, March 27, 2013 7:14 AM - Donald

Hi, May I please ask what the disadvantages of using the property directly are. I was referred to this blog from another where they said you are subject to Model Injection. Please clarify on this and how it can be done. I have another example I saw where their ViewModel derives a ViewModelBase where T is the domain entity. So instead of have standalone backing properties, they have a ModelObject which is actually your domain entity. What is the best way of using your view models?

Wednesday, April 3, 2013 12:51 PM - Mike

@Donald,

It would be useful if you said which blog referred you here so I can try to understand what they are talking about.
Add your comment

If you have any comments to make about this article, please use this form to do so. Make sure that your comment relates specifically to the article above. More general comments can be posted through the form on the Contact page.

Please note, all comments are moderated, and some may not be published. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Not relevant to the article
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam
  • Anything in a language I don't understand including gibberish.

I do not pass email addresses on to spammers, so a valid one will assist me in responding to you personally if required.

Recent Comments

Allen Michaels 12/17/2014 4:37 PM
In response to Cascading DropDownLists with jQuery and ASP.NET
Fantastic thank you so much!...

Emily 12/17/2014 12:36 PM
In response to Parameterized IN clauses with ADO.NET and LINQ
Thanks, very helpful!!!! Can i use this for multiple in's ????? IN (.....) and IN(...) and IN...

sss 12/16/2014 3:06 PM
In response to Solving the Operation Must Use An Updateable Query error
good...

Gjuro 12/15/2014 10:30 PM
In response to Examining the Edit Methods and Edit View
You have one fromr (and it should be from, I suppose). :-)...

Gjuro 12/15/2014 10:27 PM
In response to Adding Search
Hi, thnx for all this C#->VB translations. Yet, the following code block is (slightly) in error it a...

Scot 12/14/2014 1:39 PM
In response to Entity Framework 6 Recipe - Alphabetical Paging In ASP.NET MVC
Thanks,Mike I found solution....

Gjuro 12/13/2014 10:52 PM
In response to Accessing Your Model's Data from a Controller
The article mentions "Creating an Entity Framework Data Model for an ASP.NET MVC Application" (at is...

Samuel 12/13/2014 8:40 AM
In response to Displaying The First n Characters Of Text
I have failed to use the extension because it throws an error that it doesn't recognise the chop be...

Ignas 12/12/2014 5:11 PM
In response to Cleaner Conditional HTML Attributes In Razor Web Pages
Any suggestions for Html Helper elements with HtmlAttributes, when you need to conditionally set it...

Gautam 12/11/2014 8:50 PM
In response to Validation In Razor Web Pages 2
Hi Mike Is this required for V3, non html helper input...