ASP.NET MVC: Making strongly-typed ViewPages more easily
MVC February 21st, 2008When I first started using ASP.NET MVC, I had a problem with ViewData. It seemed like a bit of a pain, because you have two options:
1. Treat it as a loosely-typed dictionary, in which case you get no help from intellisense and have to keep writing typecasts all over your views (which is error-prone and ugly):
Customer name: <%= ((Customer)ViewData[”Customer”]).FullName %>
2. Treat it as a specific strongly-typed object - but which type? It’s all very well to pass in one of my domain objects (as in RenderView(”ShowCustomer”, customerRecord)), but the next minute I want to add a status message or something, and the model breaks down.
Jeffrey Palermo came up with a great idea called SmartBag: it solves the problem - at least partially - by supplying an interface like ViewData.Get<CatOwner>() (which returns the first CatOwner object in the collection). This certainly reduces the scope for typos and the need for typecasts. Still, I wasn’t satisfied, because it gets awkward when you have more than one object of a given type (you have to revert to using string keys).
A helpful convention
Fairly soon, I settled down on a convention that seems effective (so far). In the code-behind file for every ViewPage, right at the top, I define a very simple data container object specific to that ViewPage:
public class ShowCatViewData { public string Name { get; set; } public int Age { get; set; } public bool HasFunnyFace { get; set; } public CatOwner Owner { get; set; } } public partial class ShowCat : ViewPage<ShowCatViewData> { }
… and then of course render the view like this:
[ControllerAction] public void Index() { RenderView("ShowCat", new ShowCatViewData { Name = "Moo-moo", Age = 6, HasFunnyFace = true }); }
Now we have strongly-typed ViewData so we get intellisense and no need for typecasts. Because the ShowCatViewData class is specific to the ShowCat view, I don’t mind adding in extra fields (e.g. for status messages or whatever) whenever they’re needed. It doesn’t interfere with my database model.
But what if my controller prefers loosely-typed dictionaries?
Admittedly, sometimes it seems nice to use the dictionary syntax to construct ViewData. You can add fields incrementally, and the controller doesn’t need to know about any particular .NET class specific to the view, like this:
[ControllerAction] public void Index() { ViewData["Age"] = 25; ViewData["HasFunnyFace"] = false; if (nameIsKnown) ViewData["Name"] = "Frankie"; RenderView("ShowCat"); }
… but you can’t do that if the ViewPage demands a strongly-typed ViewData object, right?
Introducing AutoTypeViewPage<T>
When you derive a ViewPage from AutoTypeViewPage<T>, your ViewPage suddenly gets a little bit smarter.
Just change this:
public partial class ShowCat : ViewPage<ShowCatViewData> { ... }
… to this:
public partial class ShowCat : AutoTypeViewPage<ShowCatViewData> { ... }
and now you can use any of following three syntaxes to send the ViewData, and you’ll get the exact same strongly-typed ViewData in the ASPX page:
Syntax #1: Old-school dictionary
ViewData["Name"] = "Moo-moo"; ViewData["Age"] = 6; ViewData["HasFunnyFace"] = true; RenderView("ShowCat");Syntax #2: Explicitly-typed ViewData object
RenderView("ShowCat", new ShowCatViewData { Name = "Moo-moo", Age = 6, HasFunnyFace = true });Syntax #3: Anonymously-typed object
RenderView("ShowCat", new { Name = "Moo-moo", Age = 6, HasFunnyFace = true });
How interesting! Show me the code
OK, chill out. It’s very simple:
public class AutoTypeViewPage<TViewData> : ViewPage<TViewData> { protected override void SetViewData(object viewData) { if ((viewData == null) || (typeof(TViewData).IsAssignableFrom(viewData.GetType()))) // The incoming object is already of the right type base.SetViewData(viewData); else { // Convert the incoming object to a dictionary, if it isn't one already IDictionary suppliedProps = viewData as IDictionary; if (suppliedProps == null) suppliedProps = viewData.GetType().GetProperties() .ToDictionary(pi => pi.Name, pi => pi.GetValue(viewData, null)); // Construct a TViewData object, taking values from suppliedProps where available TViewData data = Activator.CreateInstance<TViewData>(); foreach (PropertyInfo allowedProp in typeof(TViewData).GetProperties()) if (suppliedProps.Contains(allowedProp.Name)) allowedProp.SetValue(data, suppliedProps[allowedProp.Name], null); base.SetViewData(data); } } }
Final notes
Apparently MonoRail has something vaguely related: DictionaryAdapter. It’s a bit different because what that does is take a pure interface and create a basic implementation via run-time code gen, so you don’t need a concrete implementation of the ViewData class - just an interface for it. If you were clever, you could combine that with the AutoTypeViewPage technique so that arbitrary incoming objects were converted to IMyInterface.
If you have any feedback, you know where to post!

February 23rd, 2008 at 5:09 pm
[…] ASP.NET MVC: Making Strongly-Typed ViewPages More Easily (Steve Sanderson) […]
February 27th, 2008 at 8:37 pm
Have you considered adding this to MVCContrib?
February 29th, 2008 at 10:48 am
Ben, thanks for the suggestion - I’ve submitted it.
I also changed the name FlexiViewPage to AutoTypeViewPage because it’s more descriptive.
April 4th, 2008 at 8:25 pm
ViewData as an IDictionary is obviously not a strongly-typed collection. You’re exposing a misleading view of implied strongly-typed collection behavior over an underlying implementation that is obviously not strongly-typed. This is syntactic sugar and convenience methods, nothing more. Please don’t falsely advertise.
April 6th, 2008 at 6:10 pm
Sweet. Thanks for sharing, Steve!
June 19th, 2008 at 6:12 pm
It seems to me the data container object is defined in the wrong place. The fact that you define it in the view means that the controller is dependent on the view, whereas it seems to me the dependency should go the other way - that is, downstream. In many senses this is similar to a DTO being passed in an SOA scenario. For that reason, I’d either define the data container object in the controller or in a location lateral to both the view and controller.
June 20th, 2008 at 9:32 pm
Nick, that’s an interesting point. The thing is, the controller is effectively dependent on the view whichever way round you do it. The controller is choosing to render a specific view for a specific purpose; it can hardly be agnostic of the view.
I often think of a view as being like a ‘function call’ that the controller can make. It chooses to call a certain view, and that view requires a specific data structure (yes, like a DTO) to be passed as a parameter. So the DTO definition is really owned by the view more than it’s owned by the controller.
You could define the DTO in your controller if you prefer. It just makes more sense to me for it to go with the view.