Resolving InvalidCastException when two different versions of Structuremap are loaded in the same appdomain

19. September 2011 09:11 by Matt Wrock in   //  Tags:   //   Comments

Last week I was integrating my automatic css merge, minify and sprite utility, RequestReduce, into the MSDN Forums and search applications. Any time you have the opportunity to integrate a component into a new app, there are often new edge cases to explore and therefore new bugs to surface since no app is exactly the same. Especially if the application has any level of complexity.

The integration went pretty smotthly until I started getting odd Structuremap exceptions in the search application. I had never encountered these before. I had a type that was using the HybridHttpOrThreadLocalScoped Lifecycle and when structuremap attempted to create this type I received the following error:

System.InvalidCastException: Unable to cast object of type 'StructureMap.Pipeline.MainObjectCache' to type 'StructureMap.Pipeline.IObjectCache'

Well that’s odd since MainObjectCache derives from IObjectCache. This smelled to me like some sort of a version conflict. The hosing application also uses Structuremap and uses version 2.6.1 while my component RequestReduce uses 2.6.3. I use IlMerge to merge RequestReduce and its dependencies into a single dll - RequestReduce.dll. While Nuget does make deployment much more simple, I still like having just a single dll for consumers to drop into their bin.

Unfortunately, searching online for this exception turned up absolutely nothing; so I turned to Reflector. The exception was coming from the HttpContextLifecycle class and it did not take long to track down what was happening. HttpContextLifecycle includes the following code:

public static readonly string ITEM_NAME = "STRUCTUREMAP-INSTANCES";

public void EjectAll()
{
FindCache().DisposeAndClear();
}

public IObjectCache FindCache()
{
IDictionary items = findHttpDictionary();

if (!items.Contains(ITEM_NAME))
{
lock (items.SyncRoot)
{
if (!items.Contains(ITEM_NAME))
{
var cache = new MainObjectCache();
items.Add(ITEM_NAME, cache);

return cache;
}
}
}

return (IObjectCache) items[ITEM_NAME];
}

public string Scope { get { return InstanceScope.HttpContext.ToString(); } }

public static bool HasContext()
{
return HttpContext.Current != null;
}

public static void DisposeAndClearAll()
{
new HttpContextLifecycle().FindCache().DisposeAndClear();
}


protected virtual IDictionary findHttpDictionary()
{
if (!HasContext())
throw new StructureMapException(309);

return HttpContext.Current.Items;
}

Its ITEM_NAME which is the culprit here. This is a static readonly field that is the key to the object cache stored in the HttpContext. There is no means to change or override this so whichever version of Structuremap is the first to create the cache, the other version will always throw an error when retrieving the cache because while both with store an IObjectCache, they will be different versions of IObjectCache and therefore different classes altogether which will lead to an InvalidCastException when one tries to cast to the other.

The work around I came up with was to create a new class that has the same behavior as HttpContextLifecycle but uses a different key:

public class RRHttpContextLifecycle : ILifecycle
{
public static readonly string RRITEM_NAME = "RR-STRUCTUREMAP-INSTANCES";

public void EjectAll()
{
FindCache().DisposeAndClear();
}

protected virtual IDictionary findHttpDictionary()
{
if (!HttpContextLifecycle.HasContext())
throw new StructureMapException(309);

return HttpContext.Current.Items;
}

public IObjectCache FindCache()
{
var dictionary = findHttpDictionary();
if (!dictionary.Contains(RRITEM_NAME))
{
lock (dictionary.SyncRoot)
{
if (!dictionary.Contains(RRITEM_NAME))
{
var cache = new MainObjectCache();
dictionary.Add(RRITEM_NAME, cache);
return cache;
}
}
}
return (IObjectCache)dictionary[RRITEM_NAME];
}

public string Scope
{
get { return "RRHttpContextLifecycle"; }
}
}
 
As you can see, I copy most of the code from HttpContextLifecycle but use a different key for the string and scope. To get this all wired up correctly with HybridHttpOrThreadLocalScoped, I also need to subclass HttpLifecycleBase. Here is the code from HttpLifecycleBase:
 
public abstract class HttpLifecycleBase<HTTP, NONHTTP> : ILifecycle
where HTTP : ILifecycle, new()
where NONHTTP : ILifecycle, new()
{
private readonly ILifecycle _http;
private readonly ILifecycle _nonHttp;

public HttpLifecycleBase()
{
_http = new HTTP();
_nonHttp = new NONHTTP();
}

public void EjectAll()
{
_http.EjectAll();
_nonHttp.EjectAll();
}

public IObjectCache FindCache()
{
return HttpContextLifecycle.HasContext()
? _http.FindCache()
: _nonHttp.FindCache();
}

public abstract string Scope { get; }
}

All HybridHttpOrThreadLocalScoped does is derrive from HttpLifecycleBase and use HttpContextLifecycle as the HTTP cache; so I need to do the same using RRHttpContextLifecycle instead:
 
public class RRHybridLifecycle : HttpLifecycleBase<RRHttpContextLifecycle, ThreadLocalStorageLifecycle>
{
public override string Scope
{
get
{
return "RRHybridLifecycle";
}
}
}
 
Then I change my container configuration code from:
 
x.For<SqlServerStore>().HybridHttpOrThreadLocalScoped().Use<SqlServerStore>().
Ctor<IStore>().Is(y => y.GetInstance<DbDiskCache>());

to
 
x.For<SqlServerStore>().LifecycleIs(new RRHybridLifecycle()).Use<SqlServerStore>().
Ctor<IStore>().Is(y => y.GetInstance<DbDiskCache>());

This does feel particularly dirty. Copying and pasting code always feels wrong. What happens if Structuremap makes changes to the implementation of HttpContextLifecycle and I do not update my code to sync with those changes. You can see how this could become fragile. It would be nice if ITEM_NAME were not static and there was a way for derived types to override it. Or if the key name at least was appended by the version name of the Structuremap assembly.
Well until such changes are made in Structuremap, I see no better alternative to my work around.
 
I hope this is helpful to any others who have experienced this scenario. I am also very open to suggestions for a better workaround. In the meantime, I have submitted a pull request to the Structuremap repository that appends the assembly version to the the HttpContext.Items key name.
blog comments powered by Disqus

About Me

Hey thats me!

I'm Matt Wrock with over fifteen years of experience architecting scalable, distributed, high traffic web applications as well as environment and deployment automation. I currently live in Woodinville, WA with my wife, two daughters, four dogs and two cats. Until just recently I worked for Microsoft as a Sr. Software Engineer and now work for CenturyLink Cloud focusing on data center automation. I'm also project founder of http://Boxstarter.org and a committer to http://chocolatey.org.

Month List