ASP.NET MVC, session state and concurrent requests: not what you’d expect September 28, 2010
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.dlllibrary. -
Add the module into your project’s
Web.configto 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.csfile: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 toDefault),ReadOnlyandDisabled.
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!



“…perhaps an access token for flickr or SmugMug…”
Be really careful about this. Session is not directly connected to authorization, and is not secure by default. (Google “ASP.NET session hijacking.”) Security-sensitive data should generally not be stored there.
We bumped into the same 2 weeks ago. But then for a “normal” ASP.NET webforms application.
We got around it and it significantly boosted the performance!
We basically now have only have 1 aspx page that has enabled session state. It’s the page that’s used to log in; so the first page the user has to visit to be able to use the web application.
After there are made http requests to other aspx pages and web services. All of those other pages / services now have session state disabled.
But how can we still store session stuff then? We made a “custom session cache” that uses the session id as key. When the user surfs to the login page, ASP.NET generates the ASP.NET_SessionId cookie (name can be changed in web.config). In all those other pages / services we’re able to retrieve the value of that cookie and find the correct cached data in our custom session cache.
Each aspx / asmx request runs some code which stores the “last activity” datetime in the users session cache. When the application starts, we start a thread that checks which sessions were not active for a period longer then the session timeout. That allows us to clean up the custom session cache.
Something like that
[...] ASP.NET MVC, session state and concurrent requests: not what you’d expect – ‘Rudi’ explores some performance problems in an ASP.NET application, discovering a bottle neck in the application caused by Session State, resulting in only a single request from a user executing at the same time. Rudi then explores techniques for improving this in ASP.NET and ASP.NET MVC applications. [...]
Is it possible to use the MVC 3 style solution of ControllerSessionState(ControllerSessionState.ReadOnly)] with a MVC 1 site?