How to integrate Text Template Transformation Toolkit (T4) with MSBuild

18 Dec 2013 posted by Michele Leonetti

In this post I’m going to describe how to dynamically generate a c# class via T4 template and inject the values of its properties via MSBuild or web.config, based on the build type (via Visual Studio 2012 or MSBuild).
Firstly two words about Text Template Transformation Toolkit aka T4; this is one of the oldest framework used with Visual Studio (since VS 2005) in order to generate projects and any sort of files that we can add into the solution. The name is pretty talkative, this framework performs a transformation of a template file, based on parameters, and generates a default output file with the specified extension.
For further details: http://msdn.microsoft.com/en-us/library/bb126445.aspx

Based on my experience, usually T4 is set up as a transformation process which takes place manually via Visual Studio (from VS menu: Build –> transform all T4 templates) or via external processes, which use the native TextTranformation.exe file, it runs before the generation of the final artefacts in order to transform and generate all the classes and produce the final build.
My goal was to create something totally dynamic, giving the possibility to the team to change the behaviour of the platform based on some values stored into the web.config as appSettings and, at the same time, achieve the same result into TeamCity (our Continuous Integration system), injecting externally different values for different type of builds.


The first scenario is pretty easy to implement, I just had to read the web.config via some T4 classes (such as “Host” class) and assign that value to my output file. The interesting part came when I tried to pass the same values via MSBuild as properties of the build process.


Let me describe the whole scenario and immediately afterward I’ll show you few interesting lines of code.


This is my main class, T4 generated, which is shared with the backend:

public  class EnvironmentalSettings: IEnvironmentalSettings
    {
        public string ApplicationPath {get{return "MyApplicationPathValue";}}
        public string PartnerCode {get{return " MyPartnerCodeValue ";}}
        public string DefaultVersion {get{return "MyDefaultVersionValue";}}
        public bool SeoNoIndex {get{return true;}}
        public string HeaderUrl {get{return "https://myurl.co.uk";}}
    }

Once that this class is generated, T4 uses these properties to generate the output of a couple of js files (which drive the frontend part of the application).


Here it comes the first difficulty: T4 performs the transformation “all together”, how can I create dependencies from the output of a template and use the newly generated properties of my class into another template?


The answer is really easy: you can’t, or probably yes, using this directive “VolatileAssembly” (for further details you can have a quick look online); anyway based on my experience it doesn’t work with VS2012 - or probably I’m not good ? - so that I had to find an alternative way to achieve the same result.


I tried referencing the assembly of the project which contains my class and similar, but the result is always the same: during the build process, VS can’t access to the DLLs involved in the build - technically it is possible but errors of other sort will pop up “the file is locked by another process”-.


Here the solution: write a kind of “T4 partial class” which can be used to evaluate the parameters injected via MSBuild and use that to supply the property value for the other templates.

<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="Microsoft.Build.dll" #>
<#@ assembly name="System.Configuration.dll" #>
<#@ assembly name="System.Core.dll" #>
<#@ import namespace="System.Configuration" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Microsoft.Build.Execution" #>
<#
    var creditCardsPartnerCode = Host.ResolveParameterValue("-", "-", "CreditCardsPartnerCode");
    if(string.IsNullOrWhiteSpace(creditCardsPartnerCode)) 
    {
        creditCardsPartnerCode = WebConfigAppSettings(Host.ResolveAssemblyReference(webProjPath),"PartnerCode");
    }
    var creditCardsApplicationPath = Host.ResolveParameterValue("-", "-", "CreditCardsApplicationPath");
    if(string.IsNullOrWhiteSpace(creditCardsApplicationPath)) 
    {
        creditCardsApplicationPath = WebConfigAppSettings(Host.ResolveAssemblyReference(webProjPath),"ApplicationPath");
    }

    var defaultVersion = Host.ResolveParameterValue("-", "-", "CreditCardsDefaultVersion");
    if(string.IsNullOrWhiteSpace(defaultVersion)) 
    {
        defaultVersion = WebConfigAppSettings(Host.ResolveAssemblyReference(webProjPath),"DefaultVersion");
    }

    var creditCardsSeoNoIndex = Host.ResolveParameterValue("-", "-", "CreditCardsSeoNoIndex");
    if(string.IsNullOrWhiteSpace(creditCardsSeoNoIndex)) 
    {
        creditCardsSeoNoIndex = WebConfigAppSettings(Host.ResolveAssemblyReference(webProjPath),"SeoNoIndex");
    }

    var creditCardsHeaderUrl = Host.ResolveParameterValue("-", "-", "CreditCardsHeaderUrl");
    if(string.IsNullOrWhiteSpace(creditCardsHeaderUrl)) 
    {
        creditCardsHeaderUrl = WebConfigAppSettings(Host.ResolveAssemblyReference(webProjPath),"HeaderUrl");
    }
#>
<#+
public static string WebConfigAppSettings(string projectDirectory, string key)
{
    var webConfigPath = string.Format(@"{0}\{1}",projectDirectory, @"..\Web.config");
    var configFile = new ExeConfigurationFileMap { ExeConfigFilename = webConfigPath };
    var configuration = ConfigurationManager.OpenMappedExeConfiguration(configFile, System.Configuration.ConfigurationUserLevel.None);
    if (configuration.AppSettings!= null && configuration.AppSettings.Settings[key]!=null)
        return configuration.AppSettings.Settings[key].Value;

    return string.Empty;
}
#>

Let’s analyse line by line what this code is doing.

<#@ template debug="true" hostspecific="true" language="C#" #>


hostspecific="true" is a directive that allows us to use, I.E. Host.ResolveParameterValue("-", "-", "CreditCardsPartnerCode")


This accesses to the csproj file and reads a specified PropertyGroup property.


The method: WebConfigAppSettings reads the required appSetting value from the web.config. Once again here I’m using this class: Host.ResolveAssemblyReference(webProjPath) which loads the csproj file.


Here comes the little gem, as we saw, reading the web.Config is pretty easy but how do I managed to read the parameters passed through MSBuild - I.E. msbuild.exe myproject /p: ApplicationPath=MyApplicationPath - ?


Well I found this little trick, into the csproj file of the project which contains the transformation files I’ve added this:

<ItemGroup>
    <T4ParameterValues Include="CreditCardsApplicationPath">
      <Value>$(CreditCardsApplicationPath)</Value>
      <Visible>false</Visible>
    </T4ParameterValues>
    <T4ParameterValues Include="CreditCardsPartnerCode">
      <Value>$(CreditCardsPartnerCode)</Value>
      <Visible>false</Visible>
    </T4ParameterValues>
    <T4ParameterValues Include="CreditCardsDefaultVersion">
      <Value>$(CreditCardsDefaultVersion)</Value>
      <Visible>false</Visible>
    </T4ParameterValues>
    <T4ParameterValues Include="CreditCardsSeoNoIndex">
      <Value>$(CreditCardsSeoNoIndex)</Value>
      <Visible>false</Visible>
    </T4ParameterValues>
    <T4ParameterValues Include="CreditCardsHeaderUrl">
      <Value>$(CreditCardsHeaderUrl)</Value>
      <Visible>false</Visible>
    </T4ParameterValues>
</ItemGroup>


This means that I’m providing some values as part of the MSBuild step; values that can be read using Host.ResolveParameterValue("-", "-", "PARAMETER_NAME")


For sure you noticed the node <Visible>false</Visible>, this will avoid that Visual Studio will show this property as a folder, under the project tree view (ultimately those properties are in an ItemGroup which is used from VisualStudio to organise files into the project).


Well this was the hardest part, now I’m just using this “T4 partial class”, like that:

<# 
    var webProjPath = string.Format("{0}{1}",System.IO.Path.GetDirectoryName(Host.TemplateFile), @"\..\..\MYPROJECT.csproj");
#>

<#@ include file="PartialEnvironmentalSettings.tt" #>
using MY_NAMESPACE.EnvironmentConfiguration;

namespace MY_NAMESPACE.EnvironmentConfiguration
{
    /* T4 AUTOGENERATED CLASS , DO NOT UPDATE MANUALLY*/
    public  class EnvironmentalSettings: IEnvironmentalSettings
    {
        public string ApplicationPath {get{return "<#=creditCardsApplicationPath #>";}}
        public string PartnerCode {get{return "<#=creditCardsPartnerCode#>";}}
        public string DefaultVersion {get{return "<#=defaultVersion#>";}}
        public bool SeoNoIndex {get{return <#=bool.Parse(creditCardsSeoNoIndex).ToString().ToLower()#>;}}
        public string HeaderUrl {get{return "<#=creditCardsHeaderUrl#>";}}
    }
}

include is the directive which allows T4 to include into a template file, another text file - in this case a “T4 partial class”.


Let’s see now how to set up the application in order to run the transformation at Build-time:


First of all you need Visual Studio 2012 professional or higher, then you need to install VS2012 SKD:

http://www.microsoft.com/en-us/download/details.aspx?id=30668

and VS2012 Visualization & Modeling SDK (this is needed to import the Microsoft.TextTemplating.targets file, which is the target to enable the transformation on build time). http://www.microsoft.com/en-us/download/details.aspx?id=30680

In order to tell Visual Studio to perform the transformation when the build occurs, I had to add these properties into the PropertyGroup node of the csproj:

<TransformOnBuild>True</TransformOnBuild>
<TransformOutOfDateOnly>false</TransformOutOfDateOnly>


The latter will perform the transformation for each tt file.


Then right click on each tt file and set the property “TransformOnBuild” to “true”.

Very last step now, add the targets:

<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TextTemplating\Microsoft.TextTemplating.targets" />

This line has to be added immediately after this:

(which is already present into the csproj file)

That’s all, now this application will be able to perform a transformation in debug mode, reading the valued from web.config or alternatively via MSBuild: msbuild myproj.csproj /p: myProperty=myvalue

Hope you enjoyed the article.

Here some useful links:
http://msdn.microsoft.com/en-us/library/vstudio/ee848143.aspx
http://msdn.microsoft.com/en-us/library/ms164309.aspx
http://blogs.msdn.com/b/t4/archive/2013/08/29/what-s-new-in-t4-for-visual-studio-2013.aspx
http://www.olegsych.com/2010/04/understanding-t4-msbuild-integration/#T4ParameterValues
http://netitude.bc3tech.net/2013/06/15/t4-gotchyas-in-your-environment/
http://msdn.microsoft.com/en-us/library/ee847423.aspx



Michele Leonetti

| Ordinary Superhero

leonetti.michele@gmail.com