Monday, September 20, 2010

Be careful: static classes can persist across HTTP Contexts!

This blog post only exists to prevent expired linking. My blog has moved to: http://pabloaizpiri.com/

--------------------------------------------------------------------------------

This morning I was doing some quick testing of some functionality I had added to our web application- (still in development)- when I realized something with security was wrong. It seems like the roles were messed up. I'd log in with one user and I was missing functionality. I tried logging back as another user and then everything was fine. I restarted the application and this time it worked with the other user but not the one I first began testing with. I could not figure out what was wrong- I hadn't touched security at all recently!

Well, today is Monday. Friday evening, right after I checked in my changes, a coworker checked in his. He had quickly added functionality to cache the role items that are used to determine a user's right. I was glad we had finally gotten to this, because this was an expensive call to the database. I didn't think much of it, it never occurred to me it had anything to do with my problem.

I started digging in slowly until finally when I logged in as a different user I noticed the roles were not refreshed. Looking into it deeper it started dawning on me that this may be because our Security class was static- and we were caching the role items in a private static variable. Surely this wasn't the case- I mean, every page call is like a new execution context, it's own HTTP Context, etc... yet it made total sense- that IS how C# operates.

Immediately I called my coworker over and we talked about it and put a proof of concept together. It was simple- a form with a text box and a label - it would call a static class that had a private static property, this property would be used to populate the label and we added functionality to update this static class' property from a button on the page.

Sure enough- even in two different browsers (Chrome and IE) if we were looking at the same page we could update the label on one browser from the other.

It makes total sense - any different case would imply a new "instance" of this static class is being created per HTTP Context or Session, which would totally violate the C# spec.

The .NET model does such a good job at abstracting away from the reality that a page is not within it's own "execution context" (so to speak). So the code on your page is not 100% segregated... it's so obvious, so simple, but it's easy to slip into thinking that it is, since everything seems to work that way.

Think about it- every time a page is called, we "get new objects" for what seems to be everything- new page, new controls, new context, etc. It's easy to forget ASP.NET is still just instantiating an HTTP Context and our Page class and passing it to the thread pool, but it is still all within the same application domain. Reminds me of one of my favorite articles from Joel, The Law of Leaky Abstractions.

Anyway... we fixed the caching- it's saved per session.But remember - it's important to understand how the ASP.NET worker process operates, and that all our classes and objects are still being instantiated within the same application domain.

Thursday, September 16, 2010

Linking to a JavaScript file...

This blog post only exists to prevent expired linking. My blog has moved to: http://pabloaizpiri.com/

--------------------------------------------------------------------------------

So we have these big JavaScript functions in one of our MasterPages. I wanted to move them to a separate file but it proved more difficult than I thought!

I could not add a "<link>" or "<script>" tag to the get and use an application relative path such as "~/Scripts/MyScript.js".

Ended up just creating the control dynamically but it was interesting how I had to arrive to the right path. Here it is:
// Retrieve Header
HtmlHead header = (this.Page.Header as HtmlHead);

// If we have access of the page header:
if (header != null)
{
    // Add script block for Master functions:
    HtmlGenericControl scrptBlck = new HtmlGenericControl("script");
    scrptBlck.Attributes.Add("src",
        VirtualPathUtility.MakeRelative(
                Request.AppRelativeCurrentExecutionFilePath,
                "~/Scripts/MasterFunctions.js"));
    scrptBlck.Attributes.Add("type", "text/javascript");
    header.Controls.Add(scrptBlck);
}