28

I've come across a curious behaviour of ASP sessions. You can force a controller to be outside of the user's session - I want to be able to do this so that multiple requests can execute at the same time and using a session causes them to execute consecutively.

Disabling session state works as expected:

[SessionState(SessionStateBehavior.Disabled)]
public class SampleController : Controller
{
    public ActionResult Test() 
    {
        // Access to the session should be denied
        object test = Session["test"];
        return Content(test);
    }
}

Going to ~/Sample/Test will throw a System.Web.HttpException, as expected. However, read-only sessions appear to behave a little strangely:

[SessionState(SessionStateBehavior.ReadOnly)]
public class SampleController : Controller
{
    public ActionResult Test() 
    {
        // Read from the session should be fine
        object test = Session["test"];
        return Content(test);
    }

    public ActionResult SetTest(string value) 
    {
        // Write to the session should fail 
        Session["test"] = value;

        // Read it back from the session
        object test = Session["test"];
        return Content(test);
    }
}

So now I expect ~/Sample/Test to work, and it does. The odd bit is that the set does too: I go to ~/Sample/SetTest?value=foo and it doesn't throw an exception, in fact it returns "foo". If I call ~/Sample/SetTest?value=bar and then ~/Sample/Test I get "bar", indicating that the session has been written to.

So in a SessionStateBehavior.ReadOnly I have successfully written to the session and read my value back.

I think this could be due to one of three things:

  • In MVC 3 [SessionState(SessionStateBehavior.ReadOnly)] is broken/ignored.
  • The [SessionState] is overridden when the session is written to and becomes writeable.
  • The SessionStateBehavior.ReadOnly actually indicates some kind of dirty/optimistic access.

Can anyone confirm?

I suspect the last one is true, based off the custom session provider documentation - if it is then how does the implementation work? Does writing to a 'read only' session risk concurrency errors (i.e. last write wins) or does it risk corrupt sessions and breaking exceptions?

Update

It looks like this is by design (from Microsoft's docs):

Note that even if the EnableSessionState attribute is marked as ReadOnly, other ASP.NET pages in the same application might be able to write to the session store, so a request for read-only session data from the store might still end up waiting for locked data to be freed.

It looks like the second option above is what it actually does - the session is locked and the mode changed to writable.

Keith
  • 150,284
  • 78
  • 298
  • 434

1 Answers1

13

~/Sample/SetTest?value=foo

Yes it won't throw any error, but it also didn't persist the session at the end of the request. By design any thing that you write to session gets updated (only if the session is writable) at the very end of the request life-cycle.

In my test ~/Sample/Test returns nothing.

I think they should have failed fast here when the session is readonly.

By the way your sample need to be rewritten

string test = (string)this.Session["test"]; 
Keith
  • 150,284
  • 78
  • 298
  • 434
chandmk
  • 3,496
  • 3
  • 21
  • 26
  • Hmm, the sample here is simplified code for what I'm doing, but I am getting the session written to - calling _~/Sample/SetTest?value=foo_ and then _~/Sample/Test_ is returning "foo" for me. Really it should error, and if not it should just fail to save the session, but it's definitely saving for me. – Keith Jan 20 '12 at 13:04
  • You're right, my sample in the question does fail to write to the session, as expected. Question is why is my app still able to write to it? I'll find out what's different and update the question... – Keith Jan 20 '12 at 13:36
  • as I mentioned in my answer above, they are not failing fast when the session is read-only and when you are updating it. Framework is simply ignoring the session update at the end of the request. Meanwhile they allow you to modify the session structure anyway you like it. – chandmk Jan 20 '12 at 14:13
  • Yeah, that's what I expect it to do and what it actually does in a fresh dummy project. However in the actual app I set the controller to be read-only and it successfully saves changes to the session across requests. So there's something missing from my question - I'm going to have to play around with the dummy project to see if I can reproduce this. – Keith Jan 20 '12 at 14:48
  • 2
    Problem found, of a sort - turns out that ASP overrides `SessionStateBehavior` from read only in certain circumstances. Most writes will just silently fail, but actions in global.ascx seem to (sometimes) force it back to write, annoyingly locking the session at an unpredictable point in the page lifecycle. I've decided to drop ASP sessions entirely - what I need is dirty read and last write wins, and it can't support that at all. – Keith Jan 24 '12 at 15:06
  • 1
    The weird thing is here I can write to the session on a readonly status, and it actually writes. And then I can read it in another controller method (also readonly) and its there. Both are non-blocking and have a 5 second sleep. Yet if I remove the attribute, both block. – Adam Tuliper Sep 15 '12 at 04:52
  • @AdamTuliper-MSFT: I think you have good timings. However, MS cannot determine these timings and obviously blocked things. – Tarik May 06 '15 at 00:39