Redirecting Assembly Versions
If you've got an ASP.NET MVC 3 project that uses libraries that were compiled against an older version of ASP.NET MVC, you can tell them to reference MVC 3 instead by adding an
assembly redirect using
<bindingRedirect> in web.config:
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="220.127.116.11-18.104.22.168" newVersion="22.214.171.124"/>
So, if you don't have the source code or the opportunity to rebuild your libraries with the MVC 3 assemblies, this'll make sure all references to MVC v1.0 or v2.0 are redirected to v3.0. In my case working on
NBlog, this fixed some discrepancies for third-party components like
Autofac.Integration.Web.Mvc.dll and DotNetOpenAuth.dll that were throwing exceptions, or just behaving strangely, because they were referencing older MVC versions.
Redirecting Assembly Names
This problem is a bit trickier. It came about when I added a project reference to
SharpBox. SharpBox has a dependency on
JSON.NET to the assembly name Newtonsoft.Json.Net40.dll, but my project already references JSON.NET by a different name,
Newtonsoft.Json.dll. So predictably, when I run the app, SharpBox can't find its JSON.NET dependency under the existing name:
Could not load file or assembly 'Newtonsoft.Json.Net40'
However, if I add a reference to both assemblies, the C# compiler throws
error CS0433 because both assemblies contain the same namespaces and types, which produces ambiguity.
The type 'Newtonsoft.Json.JsonConvert' exists in both 'Newtonsoft.Json.dll' and 'Newtonsoft.Json.Net40.dll'
There are a few possible solutions. I could grab the source code for SharpBox and rebuild it against Newtonsoft.Json.dll, but I don't want to have to do that for every new SharpBox release. Ideally, I wanted a way to automatically resolve Newtonsoft.Json.Net40
Using the <codeBase> hint
My first idea was to try an approach similar to the assembly version redirect above. But, instead of using a <bindingRedirect> I used a <codeBase> hint to try mapping
Newtonsoft.Json.Net20.dll to the existing Newtonsoft.Json.dll assembly:
<codeBase version="126.96.36.199" href="bin\Newtonsoft.Json.dll"/>
Unfortunately however, this doesn't work. Using the
Assembly Binding Log Viewer (fuslogvw.exe), it's possible to catch the binding error and view the details:
LOG: Attempting download of new URL file:///C:/Project/bin/Newtonsoft.Json.dll.
LOG: Download was successful. Attempting setup of file: C:\Project\bin\Newtonsoft.Json.dll
LOG: Entering download cache setup phase.
LOG: Name is: Newtonsoft.Json, Version=188.8.131.52, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed
WRN: Comparing the assembly name resulted in the mismatch: NAME
ERR: The assembly reference did not match the assembly definition found.
ERR: Setup failed with hr = 0x80131040.
ERR: Failed to complete setup of assembly (hr = 0x80131040). Probing terminated.
As you can see, because the assembly names don't match the binding fails.
Handling AppDomain.AssemblyResolve Event
The .NET runtime goes through several steps to
resolve an assembly reference, but even when it fails it still gives you a final chance to provide the assembly manually by firing the
AppDomain.AssemblyResolve event. The required assembly name is passed in
ResolveEventArgs.Name, so if it looks like the CLR is trying to load an assembly matching Newtonsoft.Json.*, we can return the already loaded JSON.NET assembly:
protected void Application_Start()
(s, ea) => ea.Name.StartsWith("Newtonsoft.Json") ?
Assembly.GetAssembly(typeof(Newtonsoft.Json.JsonConvert)) : null;
This is a bit of a hack, but it works fine.
Loading both at runtime
The other approach is to just have the CLR load both assemblies. Just because the C# compiler won't let us reference both
Newtonsoft.Json.dll and Newtonsoft.Json.Net40.dll, it doesn't mean you can't have both assemblies loaded at runtime.
Any second-tier dependencies like Newtonsoft.Json.Net40.dll are resolved by MSBuild at build time, so long as they're in the same folder as the assembly that uses them. In this case, they're copied along with everything else into the
/bin folder and loaded at runtime.
If, however, these dependencies are in a different folder then you can either copy them manually using a post-build event:
Or, add that folder to your probing path so the CLR knows where to look for it: