Text Transformation Toolkit Templates (T4) are frequently used to generate source code based on models, databases or other source code files. The Entity Framework entity classes and the context object are generated using T4 templates. The generated code becomes part of the currently opened solution. When the model is updated, the code will be updated on the next T4 generation as well. Generating the code only when required saves time. But what happens when somebody changes the generated code or the template is using source files which are not monitored? It will work… Until the next developer is executing the transformation again. Executing the T4 templates within the build process will show such issues early to the developer commiting the problem. But it is not that simple, when the template is using Visual Studio and the Host variable. Our solution: VisualStudioTextTransform.
The Problem
Basically we want to “automatically” transform our T4 templates as part of the build, but still use the "Host" variable and the Visual Studio automation API from within the template.
A stackoverflow answer reveals that this is not possible out-of-the-box:
However, there is a way to write a custom `TextTransform.exe` in a way to provide the neccessary services to the T4 templates.
This means the template itself doesn’t even recognize any difference between running from Visual Studio or our custom runner.
The Solution
The solution is a combinaton of two APIs provided by Visual Studio.
Custom `TextTransform.exe`
The first one is an interesting API, which allows you to hook into the Text-Transform-Engine and modify its behavior. All we need to do is to implement the “ITextTemplatingchange” interface. Details for this API and and example implementation can be found on MSDN:
- https://msdn.microsoft.com/en-us/library/bb126519.aspx
- https://msdn.microsoft.com/en-us/library/microsoft.visualstudio.texttemplating.itexttemplatingenginehost%28v=vs.110%29.aspx
EnvDTE.DTE interface
The second API we use to reach our goal is the "Automation and Extensibility for Visual Studio"-API (EnvDTE.DTE interface).
This is the interface which a lot of Visual Studio dependend templates depend on. Exactly this interface is the reason why those templates can’t be used outside of Visual Studio.
So in order to use those templates we need to provide the DTE interface from within our custom host as well.
To provide a DTE instance to the template we obviously need to either create an instance or get one from an exisiting Visual Studio instance.
Of course another solution would be to implement the interface yourself, but that’s probably a lot of overhead depending on the amount of features your templates are using.
Combining the technologies
For our purposes it’s fine if we need a running Visual Studio instance, as we are able to install Visual Studio on our build servers.
So the basic idea is simple: Spin up a Visual Studio instance, get the DTE instance and run the T4 Engine with our custom host.
However there are a lot of technical obstacles to consider:
- Our host needs to implement `IServiceProvider` to provide the `EnvDTE80.DTE2`/`EnvDTE.DTE` instance to the template-ode
- We manually need to replace `$(ProjectDir)`, `$(SolutionDir)` and `$(TargetDir)` with the respective paths (we can get those from the `dte` instance) as this is supported by the VIsualStudio Template-Engine-Host host as well.
- We need to use the same MessageFilter class as suggested by http://www.viva64.com/en/b/0169/ to automatically retry on `COMException`s.
An implementation we use to automate the T4 template generation on our build server can be found on GitHub: https://github.com/AITGmbH/VisualStudioTextTransform
Limitations
- Visual Studio has to be installed on the system (otherwise it’s a lot of implementation work, but still doable).
- Because we start a “hidden” Visual Studio instance and load the solution file this approach can be slow (depending on the system and the size of the solution).
- The program will “hang” when Visual Studio decides to open a startup dialog (for example: License, ReSharper, …). It’s a good idea to manually open Visual Studio (and check that no dialog pops up) before using this method