PTOM: Breaking Free from HttpContext


The System.Web.HttpContext class is a real heavyweight of the .NET Framework. It holds a wealth of information on the current server context, from the details of the current user request to a host of details about the server. It’s accessible from the HttpContext.Current static property, which means you can get hold of this information at and point in your code. Whether this is a strength or a weakness depends on your point of view, but consider the following code:

public class AuthenticationService
{
public IRepository Repository { get; set; }

public void Login(string username, string password)
{
User user = Repository.FindByLogin(username, password);

HttpContext.Current.Session["currentuserid"] = user.Id;
}
}

This type of code is probably pretty widespread; separate authentication code into a separate class. The problem with this type of code comes when you want to test it. Consider this test snippet:

[TestMethod]
public void Should_Retrieve_User_From_Repo()
{
_authService.Login(username, password);
_repo.AssertWasCalled(x => x.FindByLogin(username, password);
}

This will fail hard, because when your test runs, you don’t have a current HttpContext available to work with. In theory, you could fire up a webserver class and populate HttpContext.Current and everything would work just fine; with early versions of the Castle Monorail project, the Controller test support did something similar. However, this is pretty unwieldy and not to mention slow.

Of course we do have some horrible situations in which teams don’t run these kind of tests, so they’re probably thinking that they don’t care. They always run their code with a valid HttpContext available and are perfectly happy. Wait till you try and reuse your code to integrate with a third party which calls your authentication service. Ouch.

So the bottom line is that we need to make sure HttpContext.Current is kept as far away from our code as possible. Another example of HttpContext usage is something like this:

public void Log(string message)
{
WriteFile(message, DateTime.Now, HttpContext.Current.Url);
}

So we’re writing a log message along with the time and the page where the message was logged. We need this information for debugging, so it’s understandable why this code arises, but again we can see testing issues with HttpContext. Fortunately in this case it’s easy to fix:

public void Log(string message, string url)
{
WriteFile(message, DateTime.Now, url);
}

Of course this kind of solution applies to any static class you need to pull out, so let’s look at an example which is more closely related to HttpContext: cookies. A standard approach would see us doing something like this:

private void PersistUser(string encryptedUserIdentifier)
{
HttpCookie cookie = new HttpCookie("user");
cookie.Value = encryptedUserIdentifier;
cookie.Expiry = DateTime.Now.AddDays(14);
Response.Cookies.Add(cookie);
}

This does the job, and adds a cookie to the response so that the browser will acknowledge it. The problem again lies in testing this code; without an HttpContext, we’re in trouble. Because a lot of new C# code is working with ASP.NET MVC and test-first practices, we need to take that in to account in every part of our application. How about this instead:

private readonly ICookieContainer _cookies;

public Controller(ICookieContainer cookieContainer)
{
_cookies = cookieContainer;
}

private void PersistUser(string encryptedUserIdentifier)
{
_cookies.Set("user", encryptedUserIdentifier, DateTime.Now.AddDays(14));
}

Now, we don’t include any kind of code which references the cookie implementation directly, and that in turn means we don’t use HttpContext.Current. We provide an implementation of an ICookieContainer via the constructor. That interface and implementation could look like this:

public interface ICookieContainer
{
void Set(string name, string value, DateTime expires);
string Get(string name);
}

public class HttpCookieContainer : ICookieContainer
{
public void Set(string name, string value, DateTime expires)
{
HttpCookie cookie = new HttpCookie("user");
cookie.Value = encryptedUserIdentifier;
cookie.Expiry = DateTime.Now.AddDays(14);
Response.Cookies.Add(cookie);
}
}

Now, looking at this you might be wondering what on earth the point is – this is exactly the same code but in a different class! The important thing is that the first set of code is likely to be part of a bigger controller class, a class which you want to keep as thin as possible. So we pull the cookie handling code out and then the controller doesn’t have to be concerned about it at all.

Similar approaches can be used where ever HttpContext touches your code. The important thing is that because HttpContext is such a heavyweight, we can break it apart and use only the parts that are needed by wrapping them up into custom classes which can be injected where they’re needed.

Castle MicroKernel Fluent Event Wiring