Big picture thoughts on software and other topics

December 22, 2007

ASP.NET MVC A-Ha Moment: TempData == Flash

by Brian Donahue

My brother Dan just posted about our decision to try the new ASP.NET MVC CTP on a small client projectBroken Link: http://ddonahue.persistall.com/archive/2007/12/21/73.aspx.  So far, we’re having fun and haven’t hit any major roadblocks.  Today, I thought I had found a major omission in the CTP that might send us running for Monorail.  I couldn’t find an equivalent to the Rails (or Monorail) :flash object.  Well, thanks to yet another helpful (and rather timely) postBroken Link: http://www.haacked.com/archive/2007/12/21/asp.net-mvc-helpers-for-repopulating-a-form.aspx from Phil Haack, I was alerted to the TempData property on Controller.  TempData == Flash for ASP.NET MVC.  Hallelujah, crisis averted!  Why they couldn't just call it Flash in the first place I have no idea. But Dan’s point is made even clearer - it’s not just the possible missing features, bugs, and API changes you have to deal with in a CTP or EAP product - it’s the simple lack of documentation and community knowledge sharing that may cause you to not even realize that vital functionality already exists there.  This may have you spending a couple hours playing around with writing your own "FlashController."  Not that that could have possibly happened to me...

For those of you who aren’t familiar, the TempData/Flash object allows you to store some data that is meant to be used by a subsequent request.  It *only* hangs around for the next request, and then gets flushed.  It’s very useful for scenarios where you have a POST to an action, and a redirect.    In WebForms, you didn’t often come across this issue, because 99% of POST requests posted back to the same page, and you set your state and rendered.  But this is very common in MVC, and considered a best practice.  For example, you might be at the following URL:
/products/List

Which is displaying a list of products.  If you had "delete" links next to each product, they may likely point you to:
/products/delete/1

Where 1 is the Product ID.  Or if you were trying to be RESTful, you’d probably hit
/product/1
With a DELETE HTTP verb or an equivalent hidden field, method=DELETE.

But you wouldn’t expect your delete action to know to render the list view again - it only knows how to delete, and there is no "view" for deleting (this is typical of POST requests in MVC, they perform an action, and redirect you to a GET action that renders something).  Perhaps you came to it from a single product detail page, which would require a different view to render.  The typical solution is to have the Delete action redirect you to either a pre-determined page, or the referring page. 

Now, say you wanted to be able to include a message on the page you are returned to that states whether the delete was successful or not.  You could use some ugly and insecure query strings Response.Redirect("/products/list?message=it%20worked") to display "it worked."  But we’ll all agree that is awful.

Instead, you can use the Controller property TempData to set the message, knowing the next request could access it. 

TempData["errorMsg"] = "It worked!";
RedirectTo("List", "Products");

The view rendered by the redirected-to action (/products/list in this example) would then be able to have something in the view akin to

<% if (ViewContext.TempData["errorMsg"] != null){ %>
<div class="error"><%= ViewContext.TempData["errorMsg"] %></div>
<% } %>

If the TempData object’s "errorMsg" property had been set by the previous request, the message would be displayed.  When that property was null (e.g. when the page was first loaded) no message would appear.

Phil does note in his post that the ViewContext reference will be removed, and TempData will be available directly as a property of ViewPage, which will look much prettier.