Adding Session State to a Mock HttpContext Object

A while back I ran into an issue where I was running some NUnit tests that required a HttpContext object to be in place, but I didn't want to host the entire ASP.NET runtime to do this. It would've been easy to do if HttpContext wasn't sealed or, even better, implemented an interface or inherited from a base class so I could create my own custom HttpContext class, but that's not the case. I did some searching and I found this solution. The guy did a good job in getting a mock HttpContext object in place, but as soon as I started using it I found it wanting in one area: session state. If you use the code as-is, you'll notice that the Session property is null, and for our tests we needed that property [1]. It wasn't a pressing problem but when I had some time I dug into the problem, and I eventually found a solution. It only takes a few lines of code, but I'm going to walk through the path that I took to arrive at the solution.

First, I opened HttpContext in Reflector. I've said it before and I'll say it again: if you're a .NET developer you must have this tool installed. It's much easier to use than ILDasm 98% of the time and it makes it very easy to dig around in code to see what's going on behind the scenes. Anyway, my first target was to look at the Session property to see what it does when the get is invoked:

public HttpSessionState get_Session()
{
     return (HttpSessionState) this.Items["AspSession"];
}

Seems simple enough, huh? The Items property is an IDictonary-based property. All I need to do is set the right value in the dictionary to a HttpSessionState object and I'm done. The problem is that HttpSessionState does not have a public constructor, so I can't make an instance of HttpSessionState. Well...it can't be done easily. HttpSessionState has one constructor that's marked as internal:

internal HttpSessionState(string id, SessionDictionary dict, HttpStaticObjectsCollection staticObjects, 
    int timeout, bool newSession, bool isCookieless, SessionStateMode mode, bool isReadonly);

Now I have two problems. The first problem is I need to figure out the values of the parameters for this constructor. Second, I have to figure out how to call this constructor via Reflection.

The first part is a bit harder than it looks. HttpContext doesn't set the HttpSessionState value in the Items collection. I had to use my FileGenerator add-in to search all of the classes in the System.Web assembly. Once I did that, I found that SessionStateModule sets the session state value in its CompleteAcquireState() method. I won't bore you with the details on how I figured out what the parameter values are, but here's what I came up with:

HttpSessionState state = new HttpSessionState(Guid.NewGuid().ToString("N"), sessionDictionary, 
    new HttpStaticObjectsCollection(), 5, true, true, SessionStateMode.InProc, false);

Again, I need to call that constructor via Reflection - I'll get to that later. Most of the values are easy to see why they are what they are, but the kicker is that sessionDictionary. The second argument to HttpSessionState is a SessionDictionary. Unfortunately, now I have two more problems (it just keeps getting better, doesn't it?). SessionDictionary has no public constructors - in fact, the class itself is internal. So, I have to create a type via Activator.CreateInstance():

private const string TypeSessionDictionary = 
    "System.Web.SessionState.SessionDictionary, " + 
    "System.Web, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";

// ...

Type sessionDictionaryType = Type.GetType(TypeSessionDictionary);
object sessionDictionary = Activator.CreateInstance(sessionDictionaryType, true);

Cool! Now, I'm suddenly in business. I have all the values I need to pass to HttpSessionState's constructor. This time, I have to use a more complex version of Activator.CreateInstance(), but at the end I have a valid HttpSessionState object:

HttpStaticObjectsCollection staticObjects = new HttpStaticObjectsCollection();
Type sessionDictionaryType = Type.GetType(TypeSessionDictionary);
object sessionDictionary = Activator.CreateInstance(sessionDictionaryType, true);

HttpSessionState state = Activator.CreateInstance(
    typeof(HttpSessionState),
    BindingFlags.Public | BindingFlags.NonPublic | 
    BindingFlags.Instance | BindingFlags.CreateInstance, 
    null,  
    new object[] {Guid.NewGuid().ToString("N"), sessionDictionary, 
        staticObjects, 5, true, true, SessionStateMode.InProc, false}, 
    CultureInfo.CurrentCulture) as HttpSessionState;
this.context.Items[ContextKeyAspSession] = state;

All it takes is five lines of code. It took some time to figure it out, but now I have a mocked version of HttpContext that has session state. If you want to see the code in full, you can get it here. If you have any issues, please me know. Note that this code is very prone to problems. I've tested it against the .NET 1.1 version, but I have no guarantees that this code will always work. I'm doing things that require a high permission level. Also, I'm calling constructors and using types that are not public by design, so there's no guarantee that future versions will have the same signatures. But showing how I found out the session state object was created in the first place, hopefully you can fix the code if a future .NET version changes things.

[1] I realize in the comment section that someone posted a solution to the session state problem, but I'm not too fond of it. The solution I propose is very similar to what happens at runtime and you can use an HttpContext object directly.

* Posted at 09.06.2005 10:02:49 PM (Last Update: 11.13.2009 02:01:45 AM) | 15 comments | Link | RSS *

Comments

# Oops - Code Link, from Jason Bock at 09.07.2005 09:10:08 AM

For some reason I forgot to add the code link. I'll update the post later but for now you can get it here:

http://www.jasonbock.net/JB/Code/MockHttpContext.zip

# very cool, from mike h at 09.07.2005 10:22:33 AM

Excellent. Very cool.

# Senior Developer, from Mike B at 07.14.2006 02:16:12 PM

I'm getting "The following add-in(s) falied to load" message with the FileGenerator built with the download from this page. I have the latest Reflector version reference (4.2.0.0) as you specified. Any chance there is a newer version of FileGenerator?

Sorry, forgot to mention building with VS 2005 on .Net 2.0

# Thanks!, from Brendan Tompkins at 08.06.2007 11:15:15 AM

Thanks Jason,

I've recently found myself in the unfortunate position to do this, and your code worked like a charm!

# Thank you!, from si at 10.08.2007 11:16:13 PM

Another thank you from a grateful developer! :)

# dr., from Ivan at 11.03.2007 02:31:01 PM

Thank you for your post! It helped me so much!

# Fantastic Work, from Woric at 12.14.2007 06:57:26 PM

Thanks Jason - you've solved a problem I've been struggling with for ages. Excellent work.

# Small World, from Bob MacNeal at 02.14.2008 02:05:58 PM

Jason,
Thanks. Your code was to the solution to NUnit problems I was having and corresponding poor test coverage. After I implemented your solution, I discovered my buddy and Scrum-mate, known in some circles as Super Dave, knows you from Best Buy days. Small world.
Cheers,
Bob.

# typo, from Kevin Hakanson at 03.20.2008 12:49:44 PM

Nice article, but it looks like a small typo in the text (but not the code sample) - an extra y after Activator: Activatory.CreateInstance()

Maybe I will see you at the next Twin Cities Code Camp (if I can make it).

# Activatory is now Activator, from Jason Bock at 03.20.2008 12:57:43 PM

Thanks for the catch

# Does not work :((, from Vaibhav at 04.10.2008 01:15:24 AM

Hi,
HttpContext context = (new MockHttpContext(false)).Context;
this works
but this fails
System.Web.HttpContext.Current.Session["UserName"] = "V.jain";

Any idea how I could accomplish it?

# Great stuff, from Benjy at 08.04.2008 06:00:42 PM

Jason, thanks for this. It works superbly. Have referred to this here
http://santoshbenjamin.wordpress.com/2008/08/04/mock-httpcontext-and-session-state/

Thanks
Benjy

PS: Vaibhav, after creating the context, just use context.Session.Add() and context.Session["xyz] etc...

# Wonderful example, from Daniel Elliott at 09.09.2008 03:57:25 PM

I was sold after adding this unit test (The cache was my key concern.)

[TestMethod]
public void UseCache()
{
HttpContext context = (new MockHttpContext(false)).Context;
context.Cache.Add("test", "test value", null, DateTime.Now.AddMinutes(1), TimeSpan.Zero, CacheItemPriority.Normal, null);
Assert.AreEqual(context.Cache.Get("test").ToString(), "test value");
Assert.AreEqual(HttpContext.Current.Cache.Get("test").ToString(), "test value");
}

# Creating an ApplicationState, from Anthony Main at 04.08.2009 10:27:59 AM

I've been using your MockhttpContext in a few projects as a test base, its awesome good effort!

But in my current application Im trying to test teh Application Context instead of just the session. I've been digging through the reflector classes but can't find how to set the application state the way you set the session state.

Any pointers would be great

# Tyres Dealer, from Tyres Dealer at 11.13.2009 02:01:42 AM

That's great, I never thought about Adding Session State to a Mock like that before.

Comments are closed

Quote
"Good science is always the enemy of lies." P. Z. Myers
Twitter History
follow me on Twitter
Blog History