1 Aug 2012

Making Asynchronous / Concurrent calls to MVC - requests are synchronized

After a while coding in ASP.NET, I took some things for granted - the Session object for example. It's always there, so useful for storing little tidbits of stuff.

But I never pondered on the DARK SIDE OF THE SESSION, as regards its implication in MVC. Yes, I was vaguely aware that access to session data had to be synchronized (i.e. an exclusive lock placed on the session's data during access) but I didn't figure the duration of the lock. It turns out that in regular use, all MVC Controller Actions lock Session access for the duration of the request, with the reasoning that Session access could occur at any time during the request.

Therefore, if your browser makes several concurrent MVC requests - perhaps a regular View call, a polling AJAX call and an Image-Generation call - they will be run sequentially, NOT simultaneously.

Therefore, if one of your calls is slow to return, the others in the queue are held up in turn!

In old-school ASP.NET, you could turn this off at the Page level by setting EnableSessionState="false" in the @Page directive. But up til version 3, there was no equivalent in MVC apart from the radical step of disabling SessionState in the web.config file. See this post for more info on those strategies.

Well, in MVC 3 we still don't have fine-grained Sesssion control at the Action level, but we do at the Controller level, with the SessionState attribute. This allows us to say if we want to use default behaviour, ReadOnly access (concurrent reads, synchronized writes), or fully disabled Session in our Controller's Actions. With Session Disabled or ReadOnly, we have the asynchronous request ability that we desire, albeit that we have to be careful we make no calls (or at least Writes, in ReadOnly mode) to Session now that we have restricted access.

Usage is like so:

    [Authorize]
    [SessionState(System.Web.SessionState.SessionStateBehavior.Disabled)]
    public class DocumentController : Controller
    {
        public ActionResult ViewDocument(int id)
        {
            var doc = WebApp.MainService.GetDocument(id);

            if (doc == null 
                || !(doc.UploadedBy == WebApp.CurrentUser.UserId 
                    || doc.OwnerUserId == WebApp.CurrentUser.UserId 
                    || WebApp.CurrentUser.IsInRole(Core.Enums.RoleType.Admin, WebApp.CurrentSite.SiteId)))
            {
                throw new Exception("You do not have permission to view this Document");
            }

            ContentDisposition cd = new ContentDisposition { Inline = true, FileName = doc.Filename };
            Response.AppendHeader("Content-Disposition", cd.ToString());
            return File(DocumentService.GetSavedDocumentFileName(doc), FileTools.DetermineContentType(doc.Filename));
        }
    }

No comments:

Post a Comment

Comments are very welcome unless you're a spammer, in which case you should probably kill yourself.

If I helped you out today, you can buy me a beer below. Cheers!