I might have also entitled this:
“How to avoid TypeLoadException: Could not load type 'System.Runtime.CompilerServices.ExtensionAttribute' “
But I didn’t.
First, the moral of this story
I am about to take you on a debugging journey that will make some laugh and others cry and a fortunate few will travel through the entire spectrum of human emotion that will take them down into the seven bowels of hades only to be resurrected into the seven celestial states of ultimate being that will consummate in a catharsis that unifies soul, body and mind with your favorite My Little Pony character. If this does not appeal to you then know this:
If you use ILMerge to merge several assemblies into one on a machine with .Net 4.5 Beta installed and intend to have this merged assembly run on a machine running .Net 4.0, DO NOT use the following TargetPlatform switch value:
/targetplatform:"v4,c:\windows\Microsoft.NET\Framework\v4.0.30319"
Instead use this:
/targetplatform:"v4,C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0"
If you are interested in learning some details about targeting different frameworks, some nice IL debugging tips or what that means in an upgrade like 4.5 that is “in place” or does not officially change the runtime version along with some techniques for debugging such interesting scenarios, then read on.
Twas the night before Beta
So it seems serendipitous that I write this on the eve of the Visual Studio 11 launch. This last weekend I installed the beta bits on my day to day development environments. As a member of the team that owns the Visual Studio Gallery and the MSDN Code Samples Gallery and their integration with the Visual Studio IDE, I’ve been viewing bits hot out of the oven for some time now. The product seems stable and everyone seem to feel comfortable installing it side by side with VS 10. You can target .Net 4.0 so why not just dev on it full time to enjoy the full, rich dogfooding experience. At home where I do development on my OSS project RequestReduce, I work on a 5 year old Lenovo T60P laptop. Its name is dale. So the perf and memory footprint improvements of VS11 have a special appeal to me. Oh and to Dale too. Right Dale? Thought so.
Most solutions can be loaded in both VS10 and VS11 without migration
So day 1, Saturday after some initial pain of getting a XUnit test runner up and running it looks like I’m ready to go. I load up RequestReduce and all projects load up fine. I’m also happy that after loading them in VS11, they still load and compile in VS10. Next I build in VS11 and all unit and integration tests pass. Sweet! Lets get to work and make it happen.
Cut on the bleeding edge
So I had been exchanging emails all week with a developer having issues with getting RequestReduce to play nice with the Azure CDN. Turns out Azure handles CDN URLs differently from the other CDNs I have worked with in the past by requiring all CDN content to be placed in a CDN directory on the origin server. However the CDN URL should not include the CDN directory in the path. There were some minor changes I needed to make to get the RequestReduce API to work nice with this setup. Just to be sure my changes were good, I spun up a new Azure instance and created a CDN endpoint. Then I deployed my test app with RequestReduce plugged in to do bundling and minification and WHAT?!
[TypeLoadException: Could not load type 'System.Runtime.CompilerServices.ExtensionAttribute' from assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.]
This doesn’t look good and my first reaction is “Stupid Azure.” But I have users who report that RequestReduce runs fine on azure. To double check I run the same test app on my dev box, all is good. So despite the promises that .net 4.5 and VS11 will run fine on .net 4.0, I create a clean VM with .Net 4.0 and run my test app there. Yep. I get the same error. I promptly shout to my secretary, “Marge, get me building 41 and cancel my tennis match with Soma!” Perhaps a cunning power play like this will motivate him to light a proverbial fire under the sorry devs that are causing these exceptions to be thrown. Then it all hits me: its Saturday, I have no secretary and lets be honest, I wont be playing tennis with Soma (head honcho at MS DevDiv that pumps out Visual Studio) any time soon. I weep.
Debugging Begins
I soon wake up recalling that I don’t even like tennis and I’m ready to fight. I’m gonna win this. From the stack trace I can see that the error is coming from my StructureMap container setup. StructureMap is a very cool IOC container used for managing Dependency Injection. It allows you to write code that references types by their interfaces so that you can easily swap out concrete implementation classes via configuration or API. There are many reasons for doing this that is beyond the scope of this post, but the most widely used application I use it for is testing. If I have all my services and dependencies handed to me from StructureMap as Interfaces, then I can create tests that also pass in interfaces with mock implementations. Now I have tests that don’t have to worry if all that code in the dependent services actually works. I have other tests that test those. I can control the data that these services will pass into the method I am testing and that allows my test to hyper focus on just the code in my method I’m testing. If this sounds unfamiliar or confusing, I urge you to research the topic. It transformed the way I write code for the better.
So one thing StructureMap has to do to make all of this possible at app startup time is scan my assemblies for interfaces and the concrete classes that I tell it to use. It is here where I am running into this TypeLoadException. So who are you 'System.Runtime.CompilerServices.ExtensionAttribute' and what is your game? Why do you play with me?
After some googling, I get a little dirt on this Type. Apparently it is an Attribute that decorates any class that contains an extension method In C#, you will never have to include this attribute directly. The compiler will do it for you. You will see it in the IL. Well this type has moved assemblies in .Net 4.5. Apparently it was too good for System.Core and has moved to an executive suite in mscorelib. This is all complicated by the fact that the upgrade from 4.0 to 4.5 is what they call in “the industry” an In Place Upgrade. That means that you will not see a v4.5.078362849 folder in your c:\windows\Microsoft.Net\Framework directory. No. The 4.5 upgrade gets paved over the 4.0 bits and simply updates the DLLs in the C:\Windows\Microsoft.NET\Framework\v4.0.30319 folder. Not a real fan of this and I don’t know what the reasoning is but that’s how its done.
So now I’m thinking that this must be some edge case caused by StructureMap using Reflection in such a way where it demands to load types from what assemblies are there at compile time. I should also mention that I have a class in my assembly that has an extension method. So I find a way to tell StructureMap to go ahead and scan the assemblies but just don’t worry about any attributes. Since you can decorate classes with special StructureMap attributes that tell StructureMap that a class is a type that can be plugged into a specific interface, it will try to load all attributes it finds to see if it is one of these special attributes. Well I don’t use those so I tell StructureMap IgnoreStructureMapAttributes().
Ahhh. I am convinced that I “have it.” while my code builds and deploys to my VM all on my 5 year old laptop (remember Dale?), I have time to file a Connect bug nice and snug in the self righteous knowledge that I am doing the right thing. I have been wronged by these bleeding edge bits but I’m not angry. I am simply informing the authorities of the incident in the hopes that others will not need to suffer the same fate.
Ok. My code has now been built and deployed and is ready to run. Its gonna be great. I’ll see my asp.net web page with bundled and minified javascript and I can move on with my weekend chores. I launch the test app in a browser and…Criminey! Same error but different stack trace. Now its coming from code that Instantiates a ResourceManager. This is not instilling confidence. This isn’t even technically my code. It is code auto generated by VS when you add Resources to the Resources tabs in the VS Project properties. Really? VS’s own code isn’t even backwards compatible? The rubbish I have to work with. So it turns out the ResourceManager does something similar as StructureMap’s initialization, it scans an assembly for resources. It iterates over every type to see if it matches what you have told the ResourceManager what to look for. Ok Ok. I guess I’m just gonna have to refactor this too. And what next? When does it stop? When is enough enough?!
So I do refactor my Resource.
using (var myStream = Assembly.GetExecutingAssembly() .GetManifestResourceStream("RequestReduce.Resources.Dashboard.html")){ using (var myReader = new StreamReader(myStream)) { dashboard = myReader.ReadToEnd(); }}
If you are familiar with RequestReduce, this is the HTML page that is the RequestReduce Dashboard. I embed this as a resource in the RequestReduce DLL. Now I load it through a ManifestResourceStream into a static string and to be honest this does seem much more efficient than scanning every class for resources when I know exactly which file contains my resource and only need to load the contents of that file.
So I build and deploy again. And now, sweet victory, I see the beautiful blue background that is the default ASP.NET project home page and I see my minified CSS. But wait…oh no…something’s not right. The navigation tabs are stacked on top of each other and don’t look like tabs at all. Where is my JavascriptjQuery152004988980293273926_1358662903428 It is completely gone? If RequestReduce has a zero byte string after minification (maybe it was just a comment that gets minified out) then it will remove the script all together. So diving into the code deeper and running several tests to narrow the possibilities, I discover that the call into the MS Ajax minifier returns an empty string. Now that’s Minification!!
Its not .net 4.5’s fault
So all of the sudden I begin to wonder. Oh no! Is this not the Framework itself causing this mayhem but perhaps related to my use of ILMerge.exe which takes RequestReduce.dll, AjaxMin.dll, Structuremap.Dll and nQuant.dll and merges them all into one RequestReduce.Dll? I do this because the principle behind RequestReduce is to make Website optimizations as easy and automatic as dropping a single DLL into your bin. After I replace my merged DLL with the original unmerged ones in my test app on my .Net 4.0 VM, everything magically works. So I know now that it is either a problem in ILMerge or perhaps still a problem in the framework that is surfaced when interacting with ILMerge. Either way I want to get back up and running so I need to figure out what is going on with AjaxMin specifically. Is there something I can fix with the way I use AjaxMin or can I figure the more root problem with the Framework or ILMerge? Wouldn’t it be great if I were using an old version of ILMerge and simply updating it would fix everything?
I am using a version of ILMerge that was last updated in May and there have been two updates since the last being in November. I’m hopeful that since November was after the Preview Release of Visual Studio 11, this latest update will address .Net 4.5 issues. I update my ILMerge bits and alas, the problem still exists. So now I’m hoping that some thumbing through the ILMerge documentation or searching online will turn up some clues. Nothing. This is always the problem with working with bleeding edge bits. You don’t often get to learn from other peoples problems. You are the other people. I write this today as that “other person” who will hopefully shine light on your problem.
In a last ditch effort I send an email to Mike Barnett, the creator of ILMerge in hopes he may have come across this before and can provide guidance to get me around the issue and on my way to running code. He responded as I expected. He hadn’t played with the 4.5 bits yet and was not surprised that there would be issues especially given the fact of the in place upgrade. He was gracious enough to offer to look at the problem if I could provide him with the breaking code and access to a machine with.Net 4.5 installed.
I’m not one to quickly hand off problems on to someone else. First because it is rude and second I enjoy being able to solve problems on my own especially these kinds. In fact I had walked into the exact kind of problem that (while frustrated that my code was not running) I enjoy the most and often have a nack for getting to the bottom of figuring out what is going on. This is not because I am smart but because I am stubborn and off balance and will stick with problems long after smarter people have moved on with their lives.
The first thing I do is pull down the source code of the Ajax Minifier. I have a suspicion that the minifier is stumbling on the same exception I have been fighting with and it simply catches it and gracefully returns nothing. I discover that if there are any internal errors in the minification of content given to the minifier, it will store these errors away in a collection accessible from the minifier’s ErrorList property. When I inspect this property, there is one error reporting a type initialization error in Microsoft.Ajax.Utilities.StringMgr. So I look up that class and bang:
// resource manager for retrieving stringsprivate static readonly ResourceManager s_resourcesJScript = GetResourceManager(".JavaScript.JScript");private static readonly ResourceManager s_resourcesApplication = GetResourceManager(".AjaxMin");
// get the resource manager for our stringsprivate static ResourceManager GetResourceManager(string resourceName){ string ourNamespace = MethodInfo.GetCurrentMethod().DeclaringType.Namespace; // create our resource manager return new ResourceManager( ourNamespace + resourceName, Assembly.GetExecutingAssembly() );}
My friend the ResourceManager again. Unfortunately because this is not my code, I cant refactor it as easily. Sure it is an open source project that I think takes pull requests and whose owner, Ron Logan, is very responsive to bug fixes, but refactoring these run ins with ExtensionAttribute is beginning to feel like an unwinnable game of Whack-A-Mole and since the error does not occur without ILMerge, I need to figure out what is going on there instead of cleaning up after the mess. As far as I’m concerned at this point, the only viable options are to find a way to work with ILMerge that will prevent these errors or gather enough data that I can give to either Mike Barnett or the .net team to pursue further. I’m hoping for the former but thinking the later scenario is more likely.
Isolate and minify the problem space
I often find with these sorts of problems, the best thing to do at this point is to widdle down the problem space to as small of a surface area as possible. I create a new VS11 solution with two projects:
Project 1
static class Program{ static void Main() { System.Console.Out.WriteLine(Resources.String1); }
public static string AnotherTrim2(this string someString) { return someString.Trim(); }}
This project also contains a simple resource string and the auto generated file produced by Visual Studio that contains the following line of code that reproduces the error:
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager( "ReourceManUnMerged.Properties.Resources", typeof(Resources).Assembly );
Project 2
An empty class file. I just need a second assembly produced that I can merge.
I ILMerge them together on my .Net 4.5 machine and then I copy ILMerge.exe and my unmerged bits to my .Net 4.0 VM and merge the same bits on the 4.0 platform. I then run both merged versions on .net 4.0 and sure enough the one that was merged on the .Net 4.5 machine breaks and the one merged on .Net 4.0 runs just fine. I now know I can work with these assemblies to troubleshoot. With the minimal code, there is a lot les to look at and get confused by. I did mention that I get easily confused right? Hell I’m confused as I type right now. Did I also mention that I am one of the individuals responsible for deploying MSDN Win 8/Vs11 Beta documentation in the next hour? Don’t let my boss know about the whole confusion thing. Some things are better kept a secret.
Pop open the hood and look at IL
The first thing I want to do is look at both assemblies using a new tool put out by JetBrains, the makers of such popular tools like Resharper, called DotPeek. This is the equivilent of the highly popular tool Reflector except it is free. It lets you view the C# source code of the disassembled IL of any .net assembly. A very handy tool when you cannot access the source of an assembly and want to peek inside. I’m curious if ILMerge reassembled these assemblies in such a way that they have different source that would clue me in to something useful.
They do not. The only difference between the two sets of source code is that the assembly that works includes a reference to System.Core – the .Net 4.0 home of the offending ExtensionAttribute. While this is interesting at one level, its not very actionable data since my source code explicitly references System.Core. So I cant just add the reference and expect things to fix themselves.
Next thing I do is I use ILDasm.exe, a tool that ships with the .Net SDK that can decompile a .Net DLL down to its IL code. Ive mentioned IL a couple of times now. This is the Intermediate Language that all .net languages get compiled to. I use this tool to view the actual IL emitted by ILMerge’s compile. You can access this tool from any VS command prompt or from C:\Program Files\Microsoft SDKs\Windows. Now I’m seeing something more interesting. The two sets of IL are identical except the one merged on 4.5 contains three instances of this line:
.custom instance void [mscorlib] System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
and the one merged on 4.0 has the same line but slightly different:
.custom instance void [System.Core] System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 )
See the difference? Now I’m thinking that one option I have is to modify my build script to use ILDasm to extract the IL, do a simple string replacement to get ExtensionAttribute to load from System.Core and then use ILAsm to reassemble the transformed IL back to a usable assembly. This is certainly a workable option but it does not feel ideal. What would be ideal is to find some way to tell ILMerge to use System.Core instead of mscorelib. Oh ILMerge, can’t we work together on this. Why must we fight. I love you, you love me, we’re a happy fam… Whoa. Where am I…oh yeah. When do I need to deploy those Beta bits?
What’s the compiler doing and how to get it to target 4.0
So I ask myself what is VS doing that is different when you tell it to target .Net 4.0 as opposed to 4.5? Since it seems that the outdated 4.0 bits are simply blown away when you install 4.5, how does the compiler know to use mscorelib when targeting 4.0? If I could answer this question, maybe that would reveal something I could work with. To discover this I go to Tools/Options… in VS11 and then I select Projects and Solutions –> Build and Run on the left. This gives me options to adjust the verbosity of the MSBuild output displayed in the output window at compile time. I switch this from Detailed to Diagnostic. I want to see the actual call to CSC.exe, the C# compiler and what switches Visual Studio is passing into it. Thankfully I get just that. When targeting 4.0, the command line call looks like this:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Csc.exe /noconfig /nowarn:1701,1702,2008 /nostdlib+ /platform:AnyCPU /errorreport:prompt /warn:4 /define:DEBUG;TRACE /errorendlocation /highentropyva- /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.0\Microsoft.CSharp.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.0\mscorlib.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.0\System.Core.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.0\System.Data.DataSetExtensions.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.0\System.Data.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.0\System.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.0\System.Xml.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.0\System.Xml.Linq.dll" /debug+ /debug:full /filealign:512 /optimize- /out:obj\Debug\ReourceManUnMerged.exe /resource:obj\Debug\ReourceManUnMerged.Properties.Resources.resources /target:exe /utf8output Program.cs Properties\AssemblyInfo.cs Properties\Resources.Designer.cs "C:\Users\mwrock\AppData\Local\Temp\.NETFramework, Version=v4.0.AssemblyAttributes.cs" (TaskId:26)
When targeting 4.5 I get this:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Csc.exe /noconfig /nowarn:1701,1702,2008 /nostdlib+ /platform:AnyCPU /errorreport:prompt /warn:4 /define:DEBUG;TRACE /errorendlocation /highentropyva+ /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.5\Microsoft.CSharp.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.5\mscorlib.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.5\System.Core.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.5\System.Data.DataSetExtensions.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.5\System.Data.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.5\System.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.5\System.Xml.dll" /reference:"C:\Program Files\Reference Assemblies\Microsoft\Framework \.NETFramework\v4.5\System.Xml.Linq.dll" /debug+ /debug:full /filealign:512 /optimize- /out:obj\Debug\ReourceManUnMerged.exe /resource:obj\Debug\ReourceManUnMerged.Properties.Resources.resources /target:exe /utf8output Program.cs Properties\AssemblyInfo.cs Properties\Resources.Designer.cs "C:\Users\mwrock\AppData\Local\Temp\.NETFramework,Version=v4.5.AssemblyAttributes.cs" obj\Debug\\TemporaryGeneratedFile_E7A71F73-0F8D-4B9B-B56E-8E70B10BC5D3.cs obj\Debug\\TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs (TaskId:21)
Telling ILMerge where to find mscorelib – the devil in the details
There are a few differences here. The one that is most notable is where the framework references are being pulled from. They are not pulled from the Framework directory in c:\windows\Microsoft.Net which is where I would expect and where one usually looks for framework bits. Instead they are coming from c:\Program Files\Reference Assemblies. The MSBuild team talks about this folder here.
When you call ILMerge to merge your assemblies, you pass it a /targetplatform swich which tells it which platform to build for. Currently this switch can take v1, v1.1, v2 or v4 followed by the Framework directory. When I build for 4.0 I use this command line call via powershell:
.\Tools\ilmerge.exe /t:library /internalize /targetplatform:"v4,$env:windir\Microsoft.NET\Framework$bitness\v4.0.30319" /wildcards /out:$baseDir\RequestReduce\Nuget\Lib\net40\RequestReduce.dll "$baseDir\RequestReduce\bin\v4.0\$configuration\RequestReduce.dll" "$baseDir\RequestReduce\bin\v4.0\$configuration\AjaxMin.dll" "$baseDir\RequestReduce\bin\v4.0\$configuration\StructureMap.dll" "$baseDir\RequestReduce\bin\v4.0\$configuration\nquant.core.dll"
Most point the directory like I am to the actual platform directory that can always be located off of %windir%\Microsoft.net It’s the obvious location to use. Oh wait…hold on…
Ok I’m back. The win8 and VS11 beta docs are now deployed. Now where was I. Oh yeah…the framework directory passed to ILMerge. According to the ILMerge documentation it just needs the directory containing the correct version of mscorelib.dll. So I’m thinking, lets use this Reference Assemblies directory. I do that and re merge on .Net 4.5. I run on my 40 VM and…Hallelujah!! There’s my javascript in all of its minified glory.
I hope this helps someone out because I didn’t find any help on this error.
Also, if you have made it this far, as I promised in the beginning, you should now have a sense of unity with your favorite My Little Pony character. Mine is Bon Bon what's yours?