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 scaffolding 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 artefacts 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 or other 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.