Now that we have seen how to work with the ViewData
dictionary and strongly typed views and have learned the shortcomings of each method, let's take a look at another approach. We will now use what is called a view model to pass our data from our controller to our view.
This recipe will build on the code from the last recipe. In this case, we will use the Product
class, as well as NBuilder, to generate some Product
data for us. In this recipe, we also want to pass a Category
object to our view. To do this, we will need to add one more layer of abstraction between our business layer (currently our controller) and the presentation layer (the view) using a new view model class that can hold the current Category
and a Product
from that Category
.
Category
class in the Models
directory.Models/Category.cs:
public class Category { public string Name { get; set; } public int Id { get; set; } }
Product
view, so we will call this view model the ProductView
(or we can call it ProductViewModel)
. It will be responsible for carrying our Product
and Category
objects. Create a new ProductView
class in the Models
directory.Models/ProductView.cs:
public class ProductView { public Product CurrentProduct { get; set; } public Category CurrentCategory { get; set; } }
Product.aspx
view page. We need to update it so that the view page inherits from the System.Web.Mvc.ViewPage<ProductView>
.Views/Home/Product.aspx:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ProductView>" %> <%@ Import Namespace="{project name}.Models" %>
Product.aspx
view so that, instead of trying to display the ProductName
directly off of the view's Model, we instead call the Product
class in the Model and then the ProductName
. Then we can output the current category's name too.Views/Home/Product.aspx:
<h2><%= Model.CurrentCategory.Name %></h2> <%= Model.CurrentProduct.ProductName %>
HomeController
to our view. Do this by opening up the HomeController.cs
file. Then add code to instantiate a new Category
. After this, add the new Category
and Product
to a new instance of a ProductView
object and return that ProductView
instance to the view.Controllers/HomeController.cs:
public ActionResult Product() { Product p = Builder<Product> .CreateNew() .Build(); Category c = Builder<Category> .CreateNew() .Build(); ProductView pv = new ProductView(); pv.CurrentCategory = c; pv.CurrentProduct = p; return View(pv); }
This recipe wasn't so much about how something specifically worked, but more about explaining a specific design pattern that allows you to decouple your presentation layer away from knowing too much about your domain. Generally speaking, I would pass only view-specific objects to my view. For the most part, there is never a need for the view to know everything about a specific domain object.
Take a real life product for example; it would have name, price, and description—sure. Those are normal properties to expose to the view. But your business layer would also need to know the product's cost, weight, vendor, amount in stock, and so on. None of this information ever needs to make it out to your public site.
Also, if the view knows too much about specific domain knowledge, you will run into an issue—in that when you go to refactor your domain, you will be required to update any code referencing it. Generally speaking, you don't want information to leak across your layers (separation of concerns).
ViewModel
is not really a new pattern. You may have also heard of a Data Transfer Object or DTO. The idea of a DTO object's purpose in life is to shuttle data from one layer to another. Think of this as a contract between two layers. As long as the contract doesn't need to change, code in a specific layer can be refactored all day long with limited ripple effect throughout your code.