Sunday, 5 February 2012

A more deterministic approach to TempData in MVC

I just read the article of Greg Shackles on custom ITempDataProvider. I will follow up his blog post with my own slight adjustments and experiences of his implementation of a persistent customized TempDataProvider, using MongoDb.

The original article is here:
MongoDb based TempData provider

The default implementation of ITempDataProvider in MVC is the class SessionStateTempDataProvider. This temp data provider relies on SessionState.
As long as the end-user applies InProc as the SessionState in web.config,
TempData should work as expected. However, as soon as a move towards SqlServer
in SessionState of web.config is selected, things will start to crash..

The following is a sample implementation of how to implement a more deterministic and stable implementation, with the aid of the MongoDb object database.

The custom ITempDataProvider looks like this:


public class CustomTempDataProvider : ITempDataProvider
{
private string _collectionName;
private string _databaseName;

public CustomTempDataProvider(string databaseName, string collectionName)
{
_collectionName = collectionName;
_databaseName = databaseName;
}

#region ITempDataProvider Members

public IDictionary LoadTempData(ControllerContext controllerContext)
{
var tempDataDictionary = new Dictionary();

using (Mongo mongo = new Mongo("mongodb://127.0.0.1:27017"))
{
mongo.Connect();
IMongoCollection collection = mongo.GetDatabase(_databaseName).
GetCollection(_collectionName);
IEnumerable tempData = collection.Find(item => item.SessionIdentifier == controllerContext.HttpContext.Request.UserHostAddress).Documents;



foreach (var tempDataItem in tempData)
{
tempDataDictionary.Add(tempDataItem.Key, tempDataItem.Value);
collection.Remove(tempDataItem);
}
}

return tempDataDictionary;
}

public void SaveTempData(ControllerContext controllerContext, IDictionary values)
{
string hostName = controllerContext.HttpContext.Request.UserHostName;
using (Mongo mongo = new Mongo())
{
mongo.Connect();

IMongoCollection collection = mongo.GetDatabase(_databaseName).
GetCollection(_collectionName);

IEnumerable oldItems = collection.Find(item =>
(item.SessionIdentifier == hostName)).Documents;

foreach (var tempDataItem in oldItems)
{
collection.Remove(tempDataItem);
}

if (values != null && values.Count > 0)
{
collection.Insert(values.Select(tempDataValue => new MongoTempData
{
SessionIdentifier = controllerContext.HttpContext.Request.UserHostAddress,
Key = tempDataValue.Key,
Value = tempDataValue.Value
}));
}
}

}

#endregion
}


Beware of the code above that the SessionIdentifier is set to the UserHostAddress of the request. Rather, you might want to inject the ASP.NET SessionId or something similar here instead to avoid multiple users accessing potentially the same data. This will both crash for the next User B since User A got to the data first (TempData removes its data after it is read). In addition User A gets to read User B TempData, which could of course include sensitive data. With that word of caution, I will leave to the read to select a good identifier for the TempData.

Next up, we create a base class for setting the TempDataProvider:


public class CustomTempDataControllerBase : Controller
{
public CustomTempDataControllerBase()
{
TempDataProvider = new CustomTempDataProvider
("test", "MongoTempData");
}
}


Our controllers do then only need to inherit from this base class (note that this base class inherits from the MVC class Controller):


public class HomeController : CustomTempDataControllerBase
{

public ActionResult Index()
{
TempData["CurrentCity"] = "Trondheim";
TempData["CurrentTemperature"] = "-7 Centigrade Windchill";
return Content("TempData updated! Read the TempData here");
}

public ActionResult ReadTempData()
{
return View();
}

}


The view ReadTempData.cshtml looks like this (Razor code):
Contents of TempData:






@foreach (var tempDataItem in TempData)
{
@tempDataItem.Key @:: @tempDataItem.Value
}


That is basically all that is too it. To start up the MongoDb on your computer, download the binaries to e.g. c:\mongodb . The binaries are available from the site:
http://www.mongodb.org/

Start the database like this:
mongod --dbpath c:\mongodb\data

Next up, get the necessary binaries from the original source of this article:
http://www.gregshackles.com/2010/07/asp-net-mvc-do-you-know-where-your-tempdata-is/comment-page-1/#comment-2491

In addition I recommend the tool MongoVUE, a GUI for viewing the data inside your collections of this object oriented database (MongoDb is not a RDBMS).

Screenshot of our sample in MongoVUE:



The source code for the custom ITempDataProvider in this article is here:
Mongo Db based custom Temp Data Provider for MVC

2 comments:

  1. I even today get amazed by the level of customization the MVC framework offers. Even the TempData and other central features can be adjusted. The default SessionTempDataProvider works great for everyday use, as long as the user sticks to SessionMode InProc (the default one). Just one sidecomment, TempData works best when you know an action will return in a HTTP 302 Redirect. As you can see also in this code, we remove the values after they have been read ONCE. Only HTTP 302 is deterministic in the way that you know what the next HTTP GET or POST action will be (the HTTP 302 Redirect).

    ReplyDelete
  2. As a side note, the word "Mongo" means something very different than huMONGOus in English. It is a foul word for a person with Down's syndrome.. That being said, this Object oriented database looks very promising, although in Norway it is sometimes hard to talk about a databased called MongoDb, as this sounds like being a foul word for the uninvited.

    ReplyDelete