Avoid static variables in ASP.NET

Add to FacebookAdd to DiggAdd to Del.icio.usAdd to StumbleuponAdd to RedditAdd to BlinklistAdd to TwitterAdd to TechnoratiAdd to Yahoo BuzzAdd to Newsvine

Occasionally, I like to write static methods on classes. They are useful whenever the method loosely relates to to the class – but it doesn’t involve a specific instance of the class.

A good example of a static method in the .NET library is the int.Parse(string val) method. This method basically reads a string and tries to return an integer representation. The method logically fits with the int Class (I suppose a string could have a ToInt() instance method…but that would be overwhelming as you added ToFloat(), ToDecimal(), To…()) – but when you run it, there’s no reason to have an instance of int already. You run it to create a new instance of int.

Commonly, I follow similar logic in the business layer of my webapps. I write a static Add(param1,….,paramx) method that returns an instance of the class after it is persisted to the database. I could accomplish this other ways, but I think the resulting application code reads better like:

      User.Add(username,password);

Than:

      new User(username,password).Add();

or, worse:

      DataContext.Users.InsertOnSubmit(new User(username,password));
      DataContext.SubmitChanges();

The issue with static methods in your business logic is that you often need a common object to talk to the database through: a SqlConnection, a TableAdapter, A LINQ DataContext, etc. I could certainly create those objects locally inside each static method, but that’s time consuming and hard to maintain. I want instead to define a common property (in the business class or a parent class of it) that lazy-initializes and returns an instance of the object when I need it. The problem is that the property must also be static for a static method to have access to it.

The easiest way to accomplish this is something like:

      private static ModelDataContext dataContext=null;
      protected static ModelDataContext DataContext
      {
            get
            {
                 if(dataContext==null)
                     dataContext = new ModelDataContext();
                 return dataContext;
             }
       }

The tricky thing is that this will probably work in development and testing, until you get some load on your website. Then, you’ll start seeing all kinds of weird issues that may not even point to this code as the problem.

Why is this an issue? It’s all about how static variables are scoped inside a ASP.NET application. Most web programmers think of each page in their application as its own program. You have to manually share data between pages when you need to. So, they incorrectly assume that static variables are somehow tied to a web request or page in their application. This is totally wrong.

Really, your whole website is a single application, which spawns threads to deal with requests, and requests are dealt with by the code on the appropriate page. Think of a page in your webapp as a method inside of one big application, and not an application of its own – a method which is called by the url your visitor requests.

Why does this matter? Because static variables are not tied to any specific instance of any specific class, they must be created in the entire application’s scope. Effectively, ASP.NET static variables are the same as the global variables that all your programming teachers warned you about.

That means that, for the property above, every single request/page/user of your website will reuse the first created instance of DataContext created. That’s bad for several reasons. LINQ DataContexts cache some of the data and changes you make – you can quickly eat up memory if each instance isn’t disposed fairly quickly. TableAdapters hold open SQLConnections for reuse – so if you use enough classes of TableAdapters, you can have enough different static vars to tie up all of your db connections. Because requests can happen simultaneously, you can also end up with lots of locking/competing accesses to the variable. Etc.

What should you do about it? In my case, I take advantage of static properties that reference collections that are scoped to some appropriately short-lived object for my storage. For instance, System.Web.HttpContext.Current.Items:

      protected static ModelDataContext DataContext
      {
            get
            {
                 if(System.Web.HttpContext.Current.Items["ModelDataContext"]==null)
                     System.Web.HttpContext.Current.Items["ModelDataContext"] = new ModelDataContext();
                 return (ModelDataContext)System.Web.HttpContext.Current.Items["ModelDataContext"];
             }
       }

In this case, each instance of DataContext will automatically be disposed for each hit on the site – and DataContexts will never be shared between two users accessing the site simultaneously. You could also use collections like ViewState, Session, Cache, etc (and I’ve tried several of these). For my purposes, the HttpContext.Items collection scopes my objects for exactly where I want them to be accessible and exactly how long I want them to be alive.

Advertisements

LINQ to SQL DataContext Initializer

In LINQ to SQL, you design a model by dropping tables from your DB onto a designer surface.  The model creates a DataContext, which allows you to reference your tables in the model as (something like) a collection you can query with LINQ.  You can also call SubmitChanges() on the DataContext to persist any local changes you made back to the database.

One way to do this in the webapp would be something like:

var datacontext = new ModelDataContext();
var user = (from u in datacontext.Users where u.UserID==5 select u).First();
user.Email = "newemail@email.com";
datacontext.SubmitChanges();

This isn’t ideal, because you are peppering your webapp with data/business logic – just because you need to keep an instance of a datacontext. You could pull out the data logic, but you’d still need to new up an instance of the data context and pass it to each business logic function – that would be a mess. I’d prefer the webapp to not know/care about persistence or a DataContext at all. It would be great if the Business logic classes could handle that.

The problem is, lots of the business logic uses a static method ( User.Add(“username”,”password”) – you shouldn’t need an instance of User to run that, it works like a Factory pattern and returns an instance). A local DataContext instance would have to be static to be accessible to those functions – and ASP.NET treats local statics something like global/application variables. One instance would be shared across every request and every user on the site. DataContext instances are supposed to be available for limited amount of time – reusing one in this way would create all kinds of weird issues under any kind of load.

So, what I needed was somewhere to store the instance – shared across multiple business logic calls but scoped to exactly one request. After some trial and error, I ended up storing it in different locations for ASPX pages, for ASMX webservices (standard, and WSE 3.0), and for non-ASP.NET uses (particularly LinqPad). First, I used a interface to implement for each type and cast to get the DataContext:

public interface IDataContextHelper
{
	ModelDataContext DataContext { get; }
}

Next, I implemented that handler for the special page class that all ASPX pages use:

public class FoliotekPage : System.Web.UI.Page, Foliotek.DataAccess.IDataContextHelper
{
.
.
.

	private Foliotek.DataAccess.ModelDataContext dataContext = null; //local and non-static, so scoped with the page handler
	public Foliotek.DataAccess.ModelDataContext DataContext
	{
		get
		{
			if (dataContext == null)
			{
				dataContext = new Foliotek.DataAccess.ModelDataContext();

			}
			return dataContext;
		}
	}
.
.
.
}

For various reasons, I didn’t have access to the CurrentHandler to do this for webservices and ashx files. Instead, I used the Items collection on the ASHX handler class, and the .Current[] collection on the SoapContext classes:

Standard webservice:

public class AjaxSupport : System.Web.Services.WebService
{

	public AjaxSupport()
	{
		this.Context.Items["ModelDataContext"] = new Foliotek.DataAccess.ModelDataContext();
	}
.
.
.
}

WSE 3.0 webservice (security object)

SoapContext.Current["ModelDataContext"] = new dac.ModelDataContext();

Finally, I tied it all together by writing a property in the super class for all of my business logic classes:

static ModelDataContext datacontext;
internal static ModelDataContext DataContext
{
	get
	{

		if (System.Web.HttpContext.Current == null) // for linqpad/etc
		{
			if (datacontext == null)
				datacontext = new ModelDataContext();
			return datacontext;
		}
		else if (System.Web.HttpContext.Current.CurrentHandler is IDataContextHelper) // for web page
		{
			return ((IDataContextHelper)System.Web.HttpContext.Current.CurrentHandler).DataContext;
		}
		else if (System.Web.HttpContext.Current.Items != null && System.Web.HttpContext.Current.Items["ModelDataContext"] != null) // for simple webservice
		{
			return (ModelDataContext)System.Web.HttpContext.Current.Items["ModelDataContext"];
		}
		else if (Microsoft.Web.Services3.SoapContext.Current != null) // for webservices (in auth)
		{
			return (ModelDataContext)Microsoft.Web.Services3.SoapContext.Current["ModelDataContext"];
		}
		else if (Microsoft.Web.Services3.RequestSoapContext.Current != null) // for webservices (in webservice call)
		{
			return (ModelDataContext)Microsoft.Web.Services3.RequestSoapContext.Current["ModelDataContext"];
		}
		else
		{ // make sure we know about it if something uses the static context, that can be bad under load...
			ErrorLog.Add(-1, -1, -1, "Warning - Using Static Data Context","",
			"", "", 80, System.Web.HttpContext.Current.Request.Url+"", "", "", new 	Foliotek.DataAccess.ErrorLog.QueryValueDataTable(), "",
			"", "", "", "", "");
			if (datacontext == null)
				datacontext = new ModelDataContext();
			return datacontext;
		}
	}
}

I think the methodology is sound. I happened upon the Items collection on HttpContext fairly recently – its possible that is a solution that would cover all of the ASP.NET cases. You might even be able to instantiate it on Application_BeginRequest and clear it on Application_EndRequest and clear out some of the extra code. Here’s the resulting code:

Web App:

var user = Business.User.Get(userid);
user.Email = "newemail@email.com";
user.Update();

Business Class:

public static User Get(int userid)
{
	return (from u in DataContext.Users
	where u.UserID == userid
	select u).First();
}

public void Update()
{
	DataContext.SubmitChanges();
}

So, all that work had 2 main benefits: the web app developer doesn’t have to think about persistence at all, and the business logic developer doesn’t have to deal with managing a DataContext instance. Less than 100 lines of code eliminated several hundred lines of boiler plate code.