I worked more last night on my ASP.NET MVC project.  Second time sitting down to work on it.  I spent the evening dealing with ViewData, trying to find the best way to wrap everything the UI needs into a nice ViewData object.  The night didn't end well.

As I mentioned, I'm implementing a wizard.  The user will select one or more products, and then they will be prompted with additional steps for each product selected.  I wanted the View to be able to do something like this:

   1: public class StaplerReview : ViewPage<ProductWizard<Stapler>>
   2: {
   3: }
   1: <h2>Details about your selected stapler</h2>
   2: <ul>
   3:     <li>Staple Size: <%# ViewData.ActiveStep.Product.StapleSize %></li>
   4:     <li>Number of Pages: <%# ViewData.ActiveStep.Product.NumberPages %></li>
   5: </ul>
   6: <h4>
   7: You are on step <%# ViewData.ActiveStepIndex + 1 %>
   8: of <%# ViewData.StepCount %>
   9: </h4>

But man, this starting getting really, really messy.  It was hard to follow through the code and I couldn't quite get it to work the way I wanted.  The Model ended up knowing too much about how it was going to be presented.

Jeffrey Palermo to the rescue!  A strange twist of fate led me to subscribe to Jeffrey's blog tonight and read up.  Just 16 hours ago I was giving up on what I was trying to do with ViewData, and I had no ideas of where to go next.  And now Jeffrey has shown me the light.

Here's what Jeffrey laid out on the table:

There are some challenges with ViewData as it stands now:

  • A key is required for every object, both in the controller and view.
    ViewData.Add("conference", conference);
  • A cast is required to pull out object by key.
    (ScheduledConference)ViewData["conference"]
  • The ViewPage<T> solution discards the valuable flexibility of the objectbag being passed to the view.
    <%=ViewData.DaysUntilStart.ToString() %> where ViewData is of type ScheduledConference

Facts (or my strong opinions):

  • Repeated keys from controller and view increase chance for typos and runtime errors.
  • Casting every extraction of an object in the view is annoying.
  • Strong-typing ViewPage only works for trivial scenarios.
    - For instance, suppose once logged in, every view will need the currently logged in user.  Perhaps the user name is displayed at the top right of the screen in the layout (master page).  Since the layout shares the viewdata with the page, we immediately have the need for a flexible container that supports multiple objects.  A strongly typed ViewPage<T> won't work without an elaborate hierarchy of presentation object that are themselves flexible object containers able to support everything needed.  Once you get there, you are almost back to the initial dictionary.

</blockquote>

His SmartBag allows any number of objects to be exposed to ViewData instead of just one.  While I will likely implement my own flavor of this, the concept is the exactly solution I needed.  Now, my ViewData can be strongly typed, but it can provide multiple objects for me, and they don't have to know about each other.  Here's what I'm thinking will be in my bag:

  1. Wizard Information
    1. How many steps
    2. What step am I on
    3. What was the previous step
    4. What is the next step
  2. Product object for the current step
  3. Current User information, always
    1. This is part of why I will go with my own implementation.
    2. My bag will always contain current user information, or what I have always called 'CurrentContext' in basically every app I've written this century.

The View will then be capable of doing this:

   1: // ViewBase inherits from ViewPage<CurrentContext>
   2: public class StaplerReview : ViewBase
   3: {
   4: }
   1: <h2>Details about your selected stapler</h2>
   2: <ul>
   3:   <li>Staple Size: <%# ViewData.Get<Stapler>().StapleSize %></li>
   4:   <li>Number of Pages: <%# ViewData.Get<Stapler>().NumberPages %></li>
   5: </ul>
   6: <h4>
   7: You are on step <%# ViewData.Get<Wizard>().ActiveStepIndex + 1 %>
   8: of <%# ViewData.Get<Wizard>().StepCount %>
   9: </h4>

I feel much better about this implementation.