TempData Improvements

Today, the release of ASP.NET MVC 2 Beta was announced. It’s packed with new features including client validation, an empty MVC project, and the asynchronous controller to name a few. You can visit the links below to learn more about the new features.

The Beta is only available for Visual Studio 2008. If you are using Visual Studio 2010 Beta 2 then you will need to be a bit more patient to explore these features. The Visual Studio 2010 release cycle is different from MVC and we only update the MVC bits when a new version of 2010 is released.

Apart from adding new features, we also try to improve existing ones when making a new prerelease version available. One such improvement we made in the Beta was around the behavior of TempData. TempData is used to store information that can be consumed in subsequent requests. Conceptually, TempData is MVC’s equivalent of the Flash in Ruby on Rails (RoR); barring a few behavioral differences that will be pointed out. Basic scenarios using the Post Redirect Get (PRG) pattern are well supported in MVC today, but a number of shortcomings were identified and addressed in the latest release. The code snippet below is an example of a scenario that’s supported in both MVC 1 and 2.

   1:  [AcceptVerbs(HttpVerbs.Post)]
   2:  public ActionResult Update(Person person) {
   3:      try {
   4:          /* Do some work */
   5:          TempData["Message"] = "Success";
   6:          return RedirectToAction("Result");
   7:      }
   8:      catch {
   9:          TempData["Message"] = "Update Failed";
  10:          return RedirectToAction("Result");
  11:      }
  12:  }
  13:   
  14:  public ActionResult Result() {
  15:      return View();
  16:  }

Assuming that the Result view simply renders <%= TempData["Message"] %>. the message will disappear when the user hits F5 to refresh the view, irrespective of whether or not the Update action succeeded. No problem here; that’s the expected behavior.

 

Old Behavior

Before examining the problematic scenarios, let’s look at how TempDataDictionary behaved prior to MCV 2 Beta.

  1. When an action method is invoked, the controller calls TempData.Load() to retrieve the TempDataDictionary using the current provider (SessionStateTempDataProvider by default). All the initial keys that are present in the dictionary are stored in a HashSet, X.
  2. A separate HashSet, Y, is used by TempDataDictionary to track the keys of new items that are inserted into TempData. It also tracks existing items (items that were created in the previous request) when they are updated.
  3. The controller calls TempData.Save() at the end of the request once the action method has completed. Keys in X that are not in Y are removed and the dictionary is persisted to storage using the provider.

There are two problems with the aforementioned behavior. First, items can be removed from TempData before they are consumed. Second, it is possible that items are retained too long in the dictionary. The three scenario’s described below should help to clarify how these issues are manifested.

Scenario 1: PRG

This is similar to the PRG scenario described earlier, except that when an error occurs, the action method directly renders a view instead of using RedirectToAction.

   1:  [AcceptVerbs(HttpVerbs.Post)]
   2:  public ActionResult Update(Person person) {
   3:      try {
   4:          /* Do some work */
   5:          TempData["Message"] = "Success";
   6:          return RedirectToAction("Update");
   7:      }
   8:      catch {
   9:          TempData["Message"] = "Update Failed";
  10:          return View();
  11:      }
  12:  }

The Update view correctly displays the contents of TempData when an error occurs. However, refreshing the page results in the value being rendered for a second time.

Scenario 2: Multiple Redirects

This scenario relates to actions that perform multiple redirects. TempData[“Foo”] is set by Action1, but will be removed at the end of Action2. Consequently, the view rendered in Action3 will not be able to display the contents of TempData[“Foo”].

   1:  public ActionResult Action1() {
   2:      TempData["Foo"] = "Bar";
   3:      return RedirectToAction("Action2");
   4:  }
   5:   
   6:  public ActionResult Action2() {
   7:      /* Do some more work */
   8:      return RedirectToAction("Action3");
   9:  }
  10:   
  11:  public ActionResult Action3() {
  12:      return View();
  13:  }

Scenario 3: Interleaved Requests

This is a variation on the basic PRG scenario where another request is processed before an action redirects.

   1:  public ActionResult Action1() {
   2:      TempData["Foo"] = "Bar";
   3:      return RedirectToAction("Action2");
   4:  }
   5:   
   6:  public ActionResult Action2() {
   7:      return View();
   8:  }

The expectation for the code above is that it should always work, but actually it can fail when another request is processed before the redirect in Action1 occurs. The rogue request could be the result of an AJAX call or something simple such as the user opening a new tab inside the browser (in which case SessionState is shared). The net result is that values in TempData can become lost.

 

TempData Changes

RoR provides a wrapper for the flash called now that addresses some of the problem scenarios highlighted earlier. Values written to flash.now can be read in the current action, but will not survive until the next request as illustrated by the snippet below:

   1:  1: flash.now[:floyd] = “Goodbye cruel world” # Only available in the current action   
   2:  2: flash[:arnie] = “I’ll be back”            # Next action can still access this value

Early during the design we considered adding a similar mechanism to TempDataDictionary. In the end we opted for something simpler that addressed the scenarios we wanted to solve while limiting the number of changes to the existing API. The outcome of the changes we made resulted in the following rules that govern how TempData operates:

  1. Items are only removed from TempData at the end of a request if they have been tagged for removal.
  2. Items are only tagged for removal when they are read.
  3. Items may be untagged by calling TempData.Keep(key).
  4. RedirectResult and RedirectToRouteResult always calls TempData.Keep().

API Changes

The only API change for TempDataDictionary was the introduction of the Keep() method and one overload.

public void Keep();
public void Keep(string key);

Calling Keep() from within an action method ensures that none of the items in TempData are removed at the end of the current request, even if they were read. The second overload can be used to retain specific items in TempData.

Side Effects

Beware when debugging an application and adding a watch that references an item in TempData. Reading a value from the dictionary will result in it being deleted at the end of the request, so it can potentially interfere with your debugging effort.

RenderAction

One of the many new features in the Beta release is the set of helper methods used to render actions. Actions that are executed using these helpers are considered to be child actions, so loading and saving TempData is deferred to the parent action. The child actions merely operate on the same instance of TempData that their parents have.

Providers

The default provider for TempData, SessionsStateTempDataProvider, can be replaced with a custom provider. Although doing this was supported since the introduction of the ITempDataProvider interface in MVC 1.0, we decided to make it a bit easier in MVC 2. We’ve introduced a new method in the Controller class that’s responsible for instantiating a provider; aptly named CreateTempDataProvider. For example, if you want to use the CookieTempDataProvider (part of MvcFutures), you only need to do the following in your controller:

   1:      public class CookieController : Controller
   2:      {
   3:          protected override ITempDataProvider CreateTempDataProvider() {
   4:              return new CookieTempDataProvider(HttpContext);
   5:          }
   6:      }

 

Enjoy

Have fun with the latest release of MVC. As always, comments and feedback are appreciated, so please visit the MVC forum if you have any questions or run into problems with the latest release.

11 Comments

Comments have been disabled for this content.