To help illustrate some of the techniques we can use to pass
ViewData from a Controller to a View, let's build a simple product listing
page:
Figure 2
We will use a CategoryID integer to filter the products that
we want to display for the page. Notice above how we are embedding the
CategoryID as part of the URL (for example: /Products/Category/2 or
/Products/Category/4).
Our product listing page is then rendering two separate
dynamic content elements. The first is the textual name of the category we are
displaying (for example: "Condiments"). The second is an HTML
<ul><li/></ul> list of product names. I've circled both
of these in red in the above screen-shot.
Below we'll look at two different approaches we can use to
implement a "ProductsController" class that processes the incoming
request, retrieves the data necessary to handle it, and then passes this data
to a "List" view to render it. The first approach we'll examine
passes the data using a late-bound dictionary object. The second approach
we'll examine passes it using a strongly-typed class.
Approach 1: Passing ViewData using the
Controller.ViewData Dictionary
The Controller base class has a "ViewData"
dictionary property that can be used to populate data that you want to pass to
a View. You add objects into the ViewData dictionary using a key/value
pattern.
Below is a ProductsController class with a
"Category" action method that implements our product listing scenario
above. Notice how it is using the category's ID parameter to lookup the
textual name of the category, as well as retrieve a list of the Products within
that category. It is storing both of these in the Controller.ViewData
collection using a "CategoryName" and "Products" key:
Figure 3
Our Category action above is then calling
RenderView("List") to indicate which view template it wants to
render. When you call RenderView like this it will pass the ViewData
dictionary to the View in order for it to render.
Implementing Our View
We will implement our List view using a
List.aspx file that lives under the \Views\Products directory of our
project. This List.aspx page will inherit the layout of the Site.Master
MasterPage under the \Views\Shared folder (right click within VS 2008 and
select Add New Item->MVC View Content Page to wire up a master page when you
create a new view page):
Figure 4
When we create our List.aspx page using the MVC View Content
Page template it derives not from the usual System.Web.UI.Page class, but
rather from the System.Web.Mvc.ViewPage base class (which is a subclass of the
existing Page class):
Figure 5
The ViewPage base class provides us with a
ViewData dictionary property that we can use within the view page to access the
data objects that were added by the Controller. We can then take these
data objects and use them to render HTML output using either server controls,
or by using <%= %> rendering code.
Implementing Our View Using Server
Controls
Below is an example of how we could use the
existing <asp:literal> and <asp:repeater> server controls to
implement our HTML UI:
Figure 6
We can bind the ViewData to these controls using the below
code-behind class (note how we are using the ViewPage's ViewData dictionary
to-do this):
Figure 7
Note: Because we have no <form
runat="server"> on the page, no view-state is ever emitted.
The above controls also don't automatically render any ID value - which means
that you have total control over the HTML emitted.
Implementing our View using <%= %>
Code
If you prefer to use inline rendering code to
generate the output, you can accomplish the same result as above using the
List.aspx below:
Figure 8
Note: Because ViewData is typed as a dictionary containing
"objects", we need to cast ViewData["Products"] to a
List<Product> or an IEnumerable<Product> in order to use the foreach
statement on it. I am importing both the System.Collections.Generic and
MyStore.Models namespaces on the page to avoid having to fully qualify the
List<T> and Product types.
Note: The use of the "var" keyword above is an
example of using the new C# and VB "type inference" feature in VS
2008 (read here for my previous post on this). Because we
have cast ViewData["Products"] as a List<Product> we get full
intellisense on the product variable within the List.aspx file:
Figure 9
Approach 2: Passing ViewData using Strongly Typed
Classes
In addition to supporting a late-bound dictionary approach,
the ASP.NET MVC Framework also enables you to pass strongly typed ViewData
objects from your Controller to your View. There are a couple of benefits
of using this strongly typed approach:
You avoid using strings to lookup objects, and get
compile-time checking of both your Controller and View code
You avoid the need to explicitly cast values from the
ViewData object dictionary when using strongly-typed languages like C#
You get automatic code intellisense against your ViewData
object within both the markup and code-behind of your view page
You can use code refactoring tools to help automate changes
across your app and unit-test code base
Below is a strongly typed "ProductsListViewData"
class that encapsulates the data needed for the List.aspx view to render our
product listing. It has CategoryName and Products properties (implemented
using the new C# automatic properties support):
Figure 10
We can then update our ProductsController implementation to
use this object to pass a strongly typed ViewData object to our view:
Figure 11
Notice above how we are passing our strongly
typed ProductsListViewData object to the View by adding it as an extra
parameter to the RenderView() method.
Using the View's ViewData Dictionary with
a Strongly Typed ViewData Object
The previous List.aspx view implementations we
wrote will continue to work with our updated ProductsController - no code
changes required. This is because when a strongly typed ViewData object
is passed to a View that derives from ViewPage, the ViewData dictionary will
automatically use reflection against the properties of the strongly typed
object to lookup values. So code in our view like below:
Figure 12
will automatically use reflection to retrieve
the value from the CategoryName property of the strongly typed
ProductsListViewData object we passed when calling the RenderView method.
Using the ViewPage<T> Base Class to
Strongly Type ViewData
In addition to supporting a dictionary based
ViewPage base class, the ASP.NET MVC Framework also ships with a generics based
ViewPage<T> implementation. If your View derives from
ViewPage<T> - where T indicates the type of the ViewData class the
Controller passes to the view - then the ViewData property will be strongly
typed using this class type.
For example, we could update our List.aspx.cs
code-behind class to derive not from ViewPage, but from ViewPage<ProductsListViewData>:
Figure 13
When we do this, the ViewData property on the page will
change from being a dictionary to being of type ProductsListViewData.
This means that instead of using string-based dictionary lookups to retrieve
data, we can now use strongly typed properties:
Figure 14
We can then use either a sever-control
approach, or a <%= %> rendering approach to render HTML based on this
ViewData.
Implementing Our ViewPage<T>
Implementation Using Server Controls
Below is an example of how we could use the
<asp:literal> and <asp:repeater> server controls to implement our
HTML UI. This is the exact same markup that we used when our List.aspx
page derived from ViewPage:
Figure 15
Below is what the code-behind now looks like. Note how
because we are deriving from ViewPage<ProductsListViewData> we can access
the properties directly - and we don't need to cast anything (we'll also get
refactoring tool support anytime we decide to rename one of the properties):
Figure 16
Implementing our ViewPage<T> implementation
using <%= %> Code
If you prefer to use inline rendering code to
generate the output, you can accomplish the same result as above using the
List.aspx below:
Figure 17
Using the ViewPage<T> approach we now no longer need
to use string lookups of the ViewData. Even more importantly, notice
above how we no longer need to cast any of the properties - since they are
strongly typed. This means we can write foreach (var product in
ViewData.Products) and not have to cast Products. We also get full intellisense
on the product variable within the loop:
Figure 18