Tuesday, 1 December 2009

T4 Template Reference to a Frequently Changing Assembly - Enabling Better Factoring of Templates

You use Microsoft's DSL Tools to model some part of your system, and you use T4 to code-generate everything imaginable from your model. Your templates are probably getting quite large and unwieldy. You've probably factored your templates into some include files. And you probably have a lot of methods and nested classes in "class features" blocks (<#+..#> blocks) to factor things better. But the lack of true C#/VB editor features when editing these blocks frustrates you and leads you to write shabbier code. And only being able to create nested classes and not top-level classes in these blocks is also sometimes limiting. So you create a separate C#/VB library to hold most of the code for the templates. Now you get all the editor features that ease your frustrations and help you write clean elegant code, and you can use top-level classes.

All is good until you realize the following: You cannot recompile the library after a T4 template that uses it has been run in Visual Studio until you close that instance of VS. What a bummer - that destroys the tight edit->run template->edit cycle.

The solution: A directive processor that creates a temporary copy of the referenced assembly and loads that copy for the template generation. Each time the assembly changes, a new copy is created and the new copy is loaded for the next template generation (the templating AppDomain has to be force-unloaded to make this work). The copies are lazily cleaned up when they become unused.

I've added such a directive processor to the T4Toolbox project at codeplex. You can use it as follows:

<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="CodeGenerationLibrary.dll" #>

You should then add a reference to CodeGenerationLibrary from the project that contains the T4 template and set CopyLocal=true. If you cannot set a project reference, you can of course provide the relative path to the CodeGeneration.dll and ensure that it is built before code-gen happens.

If you don't want to get the entire T4Toolbox project, the code (with some names different from the T4Toolbox version) is pasted in the DSL Tools forum here.

4 comments:

  1. Hi Oleg,

    I posted a patch on CodePlex which make the VolatileAssemblyProcessor use the ResolveAssemblyReference instead of the ResolvePath method on the Host class. This allows us to use VS macro in the path ($SolutionDir$, etc.).

    I use it on my projects and it works like a charm :)

    ReplyDelete
  2. Does this work with VS2010 RC1? I'm getting the following exception thrown after following the above instructions.

    An Exception was thrown while processing a directive named 'VolatileAssembly'. The transformation will not be run. The following Exception was thrown:
    System.TypeLoadException: Could not load type 'Microsoft.VisualStudio.TextTemplating.Interfaces.ITextTemplatingEngineHost' from assembly 'Microsoft.VisualStudio.TextTemplating.Interfaces.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
    at T4Toolbox.VolatileAssemblyProcessor.ProcessDirective(String directiveName, IDictionary`2 arguments)
    at Microsoft.VisualStudio.TextTemplating.Engine.ProcessCustomDirectives(ITextTemplatingEngineHost host, TemplateProcessingSession session, List`1 directivesToBeProcessed)

    ReplyDelete
  3. This works like a charm! I made two modifications:

    1. Only clean the temp directory when a new new copy is made
    2. if a .pdb file exists of the same name, then copy it as well

    thanks for posting this!

    ReplyDelete
  4. tangible T4 Editor V1.9 now supports this new directive and provides intellisense for assemblies referenced by it! Great work guys!

    ReplyDelete