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

Sunday 1 January 2012

Razor extension method Each

It is possible to augment razor views with not only helpers, but also razor extension methods.

In this example, an extension method which will take a template and from this template generate a rendered result of a list of the IEnumerable of items passed in for the extension method will be presented. It is named Each and was created by Phil Haack.

I will present the source code quickly to just repeat Phil Haack's code. Let's first add a new class called IndexedItemModel:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TestActionMethodSelectorAttribute.Extensions
{
public class IndexedItem<TModel>
{
public IndexedItem(int index, TModel item)
{
this.Index = index;
this.Item = item;
}

public int Index { get; private set; }
public TModel Item { get; private set; }

}
}


Let's add the extension method next:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.WebPages;

namespace TestActionMethodSelectorAttribute.Extensions
{

public static class RazorCollectionExtensions
{

public static HelperResult Each(this IEnumerable<TItem> items,
Func<IndexedItem<TItem>, HelperResult> template)
{
return new HelperResult(
writer =>
{
for (int i = 0; i < items.Count(); i++)
{
template(new IndexedItem<TItem>(i, items.ElementAt(i))).WriteTo(writer);
}
}
);
}

}

}


An example of its usage is shown next in a view:



@{
ViewBag.Title = "Home Page";

var cars = new []
{
new { name = "Audi", color = "Blue", make = "A4" },
new { name = "BMW", color = "Black", make = "M5" },
new { name = "Volvo", color = "Red", make = "240" },
new { name = "Renault", color = "Black", make = "19" }
};

}

@cars.Each(@<li>Car is an @item.Item.name @item.Item.make of color @item.Item.color</li>)



Take note of the "magic" @item property in use here. What we see here in effect is a convenient way to iterate over a list of objects, in this case an array of anonymous objects which we call "cars" and we just add in some car objects to this array using a collection initializer. Then we output this list with our new Each method which is a razor extension method and specify our template. You need to prefix the first letter in the parenthesis with the @ sign, then use @item to refer to the object. In our Each method we use IndexedModel type for each item in the list. This got both an index and an Item of type TModel (TItem) passing in our item type, in this case the anonymous type that constitutes the "cars" object.

Thanks to Phil Haack for this nice method! I like this Each method a lot. No need to write that long @foreach razor helper any more. This also should fit nicely into linq expressions like:
@Model.MyList.Where(myItem => myItem.Age > 19).Each(@
<li>@item.Name </li>)
to output a list of the name propery for all people above 19 years old in our Model.MyList property, just to take an example.

This nice method was presented by Phil Haack here:



Note for those viewing this video, not everything went smooth for Phil Haack in this demonstration, however there were lots of "hidden gems" he presented there at the MIX11 conference in Las Vegas.

Disclaimer: I added this blog post to extract the source code from his demo into an easily available sample.

It should be relatively easy now to start creating Razor extension methods as an alternative to HTML helpers and Razor html helpers in MVC by taking a look at this example. Note the use of HelperResult and WriteTo (corresponding Response.Write in ASP.NET).

Saturday 31 December 2011

Analyzing routing in your MVC application

Routing and ASP.NET MVC woes !!!

So you have created a MVC application and worked on it a bit, and now your Global.asax contains calls to a setup of your RouteCollection that has got complex logic inside it and now you need to understand your routing better and perhaps fix that particular scenario where routing does not seem to work (keep in mind for instance that the first match algorithm is used when using your MVC routing table). How to understand those routes in a better way without staring at your code. How about interpreting it at realtime using a runtime debugger for just that case. First off, download the
ASP.NET MVC Routing Debugger Visualizer tool.

Go to the following url:
http://visualstudiogallery.msdn.microsoft.com/2993e666-4534-49c8-807f-e8bffcaee7e0

Keep in mind that this should be easily installed in Visual Studio 2010 using the Tools => Extension Manager dialog in DevEnv.

Now we need to "patch" your system to allow this tool to work.

First off edit the devenv.exe.config file to allow loading remote sources:
Open the file
C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe.config

Inside the runtime xml element node add the following:
<loadFromRemoteSources enabled="true" />

Just to double check, check that the dll is added to the following folder:
C:\Program Files\Microsoft Visual Studio 10.0\Common7\Packages\Debugger\Visualizers

There should now be a file called MvcRouteVisualizer.dll in this folder, this is the library which constitutes the ASP.NET MVC Route debugger visualizer tool.

Start the MVC application with debugging turned on and add a breakpoint in a MVC controller action, I tested this tool out with the HomeController About action in my basic templated ASP.NET MVC application for instance. Hit Ctrl+D+Q (quickwatch) and add a watch to the System.Web.Routing.RouteTable.Routes object (which is a collection of the routes to monitor for our app) and hit the available magnify icon button to the right of the value column of our watch window in our debugger.

If all now went well, you should have this nifty tool to display the routes in our MVC app. This way, if the routing seem to have gone berserk and you cant understand why your nice routing table is not correctly set up, MVC Route visualizer routing tool comes to your rescue.

Here is a screenshot of this great tool, happy coding!