Friday, October 31, 2008

ASP.NET MVC and form editing scenarios

I'm a big fan of the new ASP.NET MVC framework and I've begun using it on several projects I'm delivering. Though each preview gets better and better, there are still a few scenarios that could work more smoothly. One of these is a normal form edit/post situation where the user types data into a form and posts it to a different action.

For example, the user browsers to Customers/Edit?id=1 and gets the Edit view. When submitting the form, the form posts to Customers/Update, which redirects back to Customers/List on success.

ScottGu's blog (and others') have examples for this, but most rely on a simple set of data used for the edit screen. ScottGu's example approach involves catching any exceptions during update and then re-rendering the Edit view. There are a few problems with this approach. The first is that the Url now shows Customers/Update and not Customers/Edit (so the same screen has different Urls). The second issue arises when your Edit view needs more than ViewData.Model to display. Suppose the Customer object has a Status property that you want to pick from a dropdown. To make this work, the Edit action would set ViewData["Statuses"] to the list of allowable statuses. Now, in order for Update to render the Edit view, we'd have to also load up Statuses. Now we're loading Statuses into ViewData in two places, the Edit action and the Update action.

Because of these issues, we've taken a different approach. Inside our base controller we override OnActionExecuted and look for exceptions. If an exception happened, we capture the ModelState and store it in TempData. We then redirect back to where we came from. If no exception happened and our TempData has ModelState stored in it from an invalid form attempt, we restore it.

Conceptually it's pretty simple, and it mostly works with the current beta release. It solves the two issues we had in that the Edit action is the only place the data for the Edit view is populated and since we redirect the Url is always correct.

There are still a few minor issues. One is a bug I found in DropDownList where it doesn't look at the Model to find the selected value. It is documented on CodePlex here..

I've posted a sample solution that uses this technique here. It's just the default MVC app with a new tab added to the page.

Our real application does a lot more than this, including hiding all the unit of work logic and automatically committing if there were no errors, as well as integrating a validation framework based on Castle.Components.Validator and jQuery. The extensibility of the MVC made adding all this stuff pretty easy and clean.