T4 Template error - Assembly Directive cannot locate referenced assembly in Visual Studio 2010 project.
I ran into the following error recently in Visual Studio 2010 while trying to port Phil Haack’s excellent T4CSS template which was originally built for Visual Studio 2008.
The Problem
Error Compiling transformation: Metadata file 'dotless.Core' could not be found
In “T4 speak”, this simply means that you have an Assembly directive in your T4 template but the T4 engine was not able to locate or load the referenced assembly.
In the case of the T4CSS Template, this was a showstopper for making it work in Visual Studio 2010.
On a side note:
The T4CSS template is a sweet little wrapper to allow you to use DotLessCss to generate static .css files from .less files rather than using their default HttpHandler or command-line tool. If you haven't tried DotLessCSS yet, go check it out now!
In short, it is a tool that allows you to templatize and program your CSS files so that you can use variables, expressions, and mixins within your CSS which enables rapid changes and a lot of developer-flexibility as you evolve your CSS and UI.
Back to our regularly scheduled program…
Anyhow, this post isn't about DotLessCss, its about the T4 Templates and the errors I ran into when converting them from Visual Studio 2008 to Visual Studio 2010.
In VS2010, there were quite a few changes to the T4 Template Engine; most were excellent changes, but this one bit me with T4CSS:
“Project assemblies are no longer used to resolve template assembly directives.”
In VS2008, if you wanted to reference a custom assembly in your T4 Template (.tt file) you would simply right click on your project, choose Add Reference and select that assembly. Afterwards you were allowed to use the following syntax in your T4 template to tell it to look at the local references:
<#@ assembly name="dotless.Core.dll" #>
This told the engine to look in the “usual place” for the assembly, which is your project references.
However, this is exactly what they changed in VS2010. They now basically sandbox the T4 Engine to keep your T4 assemblies separate from your project assemblies. This can come in handy if you want to support different versions of an assembly referenced both by your T4 templates and your project.
Who broke the build? Oh, Microsoft Did!
In our case, this change causes a problem since the templates are no longer compatible when upgrading to VS 2010 – thus its a breaking change. So, how do we make this work in VS 2010?
Luckily, Microsoft now offers several options for referencing assemblies from T4 Templates:
- GAC your assemblies and use Namespace Reference or Fully Qualified Type Name
- Use a hard-coded Fully Qualified UNC path
- Copy assembly to Visual Studio "Public Assemblies Folder" and use Namespace Reference or Fully Qualified Type Name.
- Use or Define a Windows Environment Variable to build a Fully Qualified UNC path.
- Use a Visual Studio Macro to build a Fully Qualified UNC path.
Option #1 & 2 were already supported in Visual Studio 2008, so if you want to keep your templates compatible with both Visual Studio versions, then you would have to adopt one of these approaches.
Yakkety Yak, use the GAC!
Option #1 requires an additional pre-build step to GAC the referenced assembly, which could be a pain. But, if you go that route, then after you GAC, all you need is a simple type name or namespace reference such as:
<#@ assembly name="dotless.Core" #>
Hard Coding aint that hard!
The other option of using hard-coded paths in Option #2 is pretty impractical in most situations since each developer would have to use the same local project folder paths, or modify this setting each time for their local machines as well as for production deployment. However, if you want to go that route, simply use the following assembly directive style:
<#@ assembly name="C:\Code\Lib\dotless.Core.dll" #>
Lets go Public!
Option #3, the Visual Studio Public Assemblies Folder, is the recommended place to put commonly used tools and libraries that are only needed for Visual Studio. Think of it like a VS-only GAC. This is likely the best place for something like dotLessCSS and is my preferred solution. However, you will need to either use an installer or a pre-build action to copy the assembly to the right folder location. Normally this is located at:
C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PublicAssemblies
Once you have copied your assembly there, you use the type name or namespace syntax again:
<#@ assembly name="dotless.Core" #>
Save the Environment!
Option #4, using a Windows Environment Variable, is interesting for enterprise use where you may have standard locations for files, but less useful for demo-code, frameworks, and products where you don't have control over the local system. The syntax for including a environment variable in your assembly directive looks like the following, just as you would expect:
<#@ assembly name="%mypath%\dotless.Core.dll" #>
“mypath” is a Windows environment variable you setup that points to some fully qualified UNC path on your system. In the right situation this can be a great solution such as one where you use a msi installer for deployment, or where you have a pre-existing environment variable you can re-use.
OMG Macros!
Finally, Option #5 is a very nice option if you want to keep your T4 template’s assembly reference local and relative to the project or solution without muddying-up your dev environment or GAC with extra deployments. An example looks like this:
<#@ assembly name="$(SolutionDir)lib\dotless.Core.dll" #>
In this example, I’m using the “SolutionDir” VS macro so I can reference an assembly in a “/lib” folder at the root of the solution. This is just one of the many macros you can use. If you are familiar with creating Pre/Post-build Event scripts, you can use its dialog to look at all of the different VS macros available.
This option gives the best solution for local assemblies without the hassle of extra installers or other setup before the build. However, its still not compatible with Visual Studio 2008, so if you have a T4 Template you want to use with both, then you may have to create multiple .tt files, one for each IDE version, or require the developer to set a value in the .tt file manually.
I’m not sure if T4 Templates support any form of compiler switches like “#if (VS2010)” statements, but it would definitely be nice in this case to switch between this option and one of the ones more compatible with VS 2008.
Conclusion
As you can see, we went from 3 options with Visual Studio 2008, to 5 options (plus one problem) with Visual Studio 2010. As a whole, I think the changes are great, but the short-term growing pains during the migration may be annoying until we get used to our new found power.
Hopefully this all made sense and was helpful to you. If nothing else, I’ll just use it as a reference the next time I need to port a T4 template to Visual Studio 2010.
Happy T4 templating, and “May the fourth be with you!”