I've been playing a little bit with ASP.NET on my own (outside of the CodeCampServer project that I'm involved with) and I'm starting to really find out the cool uses and limits of the new framework the ASP.NET team has released to us. The most awkward thing IMHO is dealing with ViewData; specifically, having to jump through hoops to get typed data out in your view when all you want to do is just use it and not really have to worry about it.
Out of the box, there's System.Web.Mvc.ViewPage which is what all of the beginner samples say to use. Then, once your needs evolve a bit (just a bit is all it takes - you grow out of that quite quickly!) you realize that you need a way to easily access typed data in the view. The MVC framework's answer to that (at least in the Preview 2 bits) is the generic version of the aforementioned ViewPage class (appropriately System.Web.Mvc.ViewPage<T>) which allows you to pass in one type to use as your ViewData. This is a step in the right direction, but it still falls short since you find yourself creating all of these new classes just to hold a bunch of different types of view data and it often ends up being one new, custom class per page/view... hardly a reuse of code there, and very (uncomfortably) reminiscent of Web Forms style development.
You need a middle-ground - a nice way to interact with the ViewData collection in a strongly-typed way, but without having to create new classes to go along with each of your new views. Jeffrey Palermo came up with a nice way of handling this with his SmartBag (that he describes in this post and subsequently implemented in the CodeCampServer project). I really liked the idea at first because it solved a problem and it did it pretty well... not to mention somebody else did the work of creating it for me so all I had to do was just use it. :) However, when I tried to start using it in my projects, it became all too clear just how much work Jeffrey had to go through to introduce it. After thinking for a bit I remembered a powerful new tool that .NET 3.0 has provided us with - Extension Methods! With these, you can tack on functionality to pretty much any class - or even interface - you want, and so I started tinkering...
What I came up with was a set of extension methods on top of the Controller.ViewData property's type (IDictionary<x,y>) to make adding the strongly-typed data a lot easier using generics... very simple, but very powerful and very helpful. Then, going back to the whole impetus for this mess - I made a matching set of extension methods on top of the ViewPage.ViewData property (which is of type ViewData, interestingly enough) to help get access to the values we so easily tucked away using the Controller.ViewData extension methods. I've been using it for a few weeks now and it seems to be working like a charm!
Without further ado, here's the code!
/// <summary>
/// Add a value to a dictionary, using its type name as the key
/// </summary>
/// <param name="dictionary" />Dictionary to add to</param>
/// <param name="data" />Data to be added</param>
public static void Add(this IDictionary<string,object> dictionary, object data)
{
// Get the object's type name to use as the key
string typeName = data.GetType().FullName;
// Check to see if it already exists and if it does, throw an exception
if (dictionary.ContainsKey(typeName))
throw new System.Data.DuplicateNameException(typeName);
// Add the data to the dictionary using the type name as the key
dictionary[typeName] = data;
}
And then, to pull it back out on the view, I added these Extension Methods to the ViewData class:
/// <summary>
/// Get the object saved in ViewData of this type.
/// </summary>
/// <typeparam name="T">Type of object to get</typeparam>
/// <param name="viewData" />ViewData collection to retrieve from</param>
/// <returns>
/// The typed data value (if found).
/// Otherwise, the default value for type T.
/// </returns>
public static T Get<T>(this ViewData viewData)
{
return Get<T>(viewData, typeof(T).FullName);
}
/// <summary>
/// Get the object saved in ViewData of this type.
/// </summary>
///Type of object to get</typeparam>
/// <param name="viewData" />ViewData collection to retrieve from</param>
/// <param name="key" />The key used to save the data.</param>
/// <returns>
/// The typed data value (if found).
/// Otherwise, the default value for type T.
/// </returns>
public static T Get<T>(this ViewData viewData, string key)
{
T returnValue;
if (viewData.ContainsDataItem(key))
returnValue = (T)viewData[key];
else
{
// Since we're in the view, just play nice
// and return the default value if it the
// key doesn't exist.
returnValue = default(T);
}
return returnValue;
}
Simple enough, but I find them to be wicked useful! I hope you will, too.
Edit: I noticed as I was looking through the CodeCampServer source tonight (just after finishing this post, no less) that Jeffrey had actually updated his SmartBag concept to something much more along the lines of this. Hey, I never said it was incredibly original, but it sure is nice to have your thoughts validated by someone else (especially one as knowledgeable as Jeffrey!).
2 comments:
Would this solution work if I needed to pass multiple objects of the same type (i.e. two different strings) to the View? Maybe I am just not reading the code correctly, but it does seem like a fairly important omission.
That's a great question! For the sake of clarity and size I omitted it in the post. But, when I implemented this in my solutions I actually add an override to Get<T>(string key) that allows you to get any of the objects in the ViewData in a strongly-typed manner. I'm not able to quickly find an example in my online code repository to point you to right now, but it is a relatively straight-forward override.
All that being said, I'd like to point out that the post was circa-Preview 2 or 3, and since then they have added the strongly-typed ViewData.Model property. That property doesn't completely do away with the need for this method, but one of the reasons I can't quickly find an example for you is because I've found myself only needing it in rare cases since the introduction of the ViewData.Model property. In fact, for most of the places I was using this code, I was able to go back and refactor it to use the ViewData.Model property instead!
HTH,
Jess
Post a Comment