
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
.
- The first thing we are going to do is create a simple
Category
class in theModels
directory.Models/Category.cs:
public class Category { public string Name { get; set; } public int Id { get; set; } }
- Next we need to create a view model. Generally, a view model is named for the view that uses it. In this case, we will be passing the view model out to our
Product
view, so we will call this view model theProductView
(or we can call itProductViewModel)
. It will be responsible for carrying ourProduct
andCategory
objects. Create a newProductView
class in theModels
directory.Models/ProductView.cs:
public class ProductView { public Product CurrentProduct { get; set; } public Category CurrentCategory { get; set; } }
- With these two new classes created, we can open up our
Product.aspx
view page. We need to update it so that the view page inherits from theSystem.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" %>
- Next, we need to update our
Product.aspx
view so that, instead of trying to display theProductName
directly off of the view's Model, we instead call theProduct
class in the Model and then theProductName
. Then we can output the current category's name too.Views/Home/Product.aspx:
<p><%= Model.CurrentCategory.Name %></p> <%= Model.CurrentProduct.ProductName %>
- Finally, we need to wire up the data that will be passed from our
HomeController
to our view. Do this by opening up theHomeController.cs
file. Then add code to instantiate a newCategory
. After this, add the newCategory
andProduct
to a new instance of aProductView
object and return thatProductView
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); }
- Now you can hit F5 and see your site open up and display the current category and current product.
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.