Using ASP.NET’s session storage and management providers for web applications is a no-brainer. User’s preferences, external API access tokens, uploaded documents, you name it, it usually makes sense. We’ll move it into the database when it gets too big, right?

Our new photo book creation and printing service, Poyomi (a little tech intro) uses the session state via MVC’s wrapper class HttpSessionStateBase which is provider as the SessionData property on a MVC controller. We store pretty much everything before the finished photo book is assembled: cover design, book’s designed pages, photo assembly preferences, perhaps an access token for flickr or SmugMug…

Poyomi has to render many preview images for a client, doing it so asynchronously by offloading the rendering work to backend servers. A single page’s design and contained photos come from a serialized object in the session itself, that is then transferred via AMQP to the queue processor. All the MVC web app has to do is to wait for the response and pass it over to the client.

Since each photo book contains many pages, browsers will try to load images using multiple concurrent requests at the same time. That shouldn’t be a problem for the massively threaded queue processor and a simple MVC app, right?

Unfortunately, no. All of the little thumbnails were being loaded sequentially and the backend was doing just a single rendering job at a time. What was going on? I took a look at IIS’ requests page (IIS manager > Home > IIS section > Worker Processes > Right click on the Pool > View Current Requests).

Huh? A single request at a time. Talk about abysmal performance.

More consequences on typical ASP.NET MVC websites

AJAX requests often get used to offload time intensive operations. In case all of your HTTP requests, AJAX or not, demand the usage of the session data, they will all block each other. In case the user decides to “cancel” the current background operation by clicking on a link to another page on your site, the execution of it will be delayed until all of the existing requests finish processing.

ASP.NET’s session state

Digging and debugging revealed that it all has to do with the way ASP.NET handles session access. You can read about it more at MSDN.

By default, no concurrent access to the session state is allowed. Even read-only requests (as far as the session is concerned) will be locked exclusively to prevent potential corruption of its state. There is a global or per-ASPX-page setting called EnableSessionState to alter this behavior: either by disabling the session state altogether… which isn’t really useful in our case… or by marking certain pages as being read-only and thus enabling parallel execution of read-only requests. Yay! But what about our cool and modern ASP.NET MVC 2 application?

Well, it can be done. But doing so requires a visit to the MvcFutures department. I’ve found a single lone blog post that describes the required changes to your MVC execution flow – presumably by a coworker of the one that programmed the solution. Essentially, it implements a new controller factory that is able to set the session state mode per the controller being executed.

Setting the session state mode in ASP.NET MVC 2

Download the MvcFutures library. Add a local reference to the Microsoft.Web.Mvc.dll library. Add the module into your project’s Web.config to load the dynamic session controller factory. Add the highlighted lines: <httpModules> <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add name="MvcDynamicSessionModule" type="Microsoft.Web.Mvc.MvcDynamicSessionModule, Microsoft.Web.Mvc, Version=2.0.0.0"/> </httpModules> <modules runAllManagedModulesForAllRequests="true"> <remove name="ScriptModule"/> <remove name="UrlRoutingModule"/> <add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add name="MvcDynamicSessionModule" type="Microsoft.Web.Mvc.MvcDynamicSessionModule, Microsoft.Web.Mvc, Version=2.0.0.0"/> </modules> Add this module after the routing module. Tell MVC to use the new dynamic session controller factory by instantiating and assigning it in your Global.asax.cs file: protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.SetControllerFactory(new MvcDynamicSessionControllerFactory()); } Use the session state attribute on your controller: [ControllerSessionState(ControllerSessionState.ReadOnly)] public class AjaxReadonlyController : Controller { Other state modes are: Required (equivalent to Default ), ReadOnly and Disabled .

Enjoy your high-performance website!

Using Autofac with DynamicSessionControllerFactory

If you are using Autofac for dependency injection (and why aren’t you?) you’ll have to nest the two controller factories. Global.asax.cs ‘ Application_Start :

var factory = new AutofacControllerFactory(ContainerProvider); var dyn = new MvcDynamicSessionControllerFactory(factory); ControllerBuilder.Current.SetControllerFactory(dyn);

Also, be warned that you cannot inject the session data object into your classes anymore. For example, if you were using something akin to:

var builder = new ContainerBuilder(); builder.Register(s => HttpContext.Current.Session).As ().HttpRequestScoped();

…then this won’t work anymore. The controller’s SessionData property is null until MVC gets around to executing your method. You’ll have to pass it to instances that were already instantiated by Autofac’s controller factory at execution time.

In conclusion

I’m sure that almost every MVC project uses the session for storage of little tokens, strings, counters… which by default blocks the execution of parallel requests. Surprisingly, almost no one blogged about this before. Hopefully this article will add some exposure to this problem.

All of this applies to ASP.NET MVC 2, the latest production ready version. Please let me know if this is going to be implemented in MVC 3 without the usage of MvcFutures!