In the last month or two I've started using T4 templates, a feature originally made available in Visual Studio 2005 though rarely used outside of code generation tools for database models and the like. Though I have known about T4 templates for quite some time, like many developers, I have struggled to conceive of a practical application in my every day software development activities. That is until recently, when two uses came along at once.
1 day, 7000 lines
The first use came when I started to implement a strongly typed unit conversion framework. I needed to generate types for different quantities (Area, Distance, Volume, etc.), units and operations that convert between them. As I was coding my framework, I noticed that much of the code was identical. Rather than cut and paste a lot (I had over 20 different quantities and hundreds of units) and deal with a horribly tedious task when bug fixing, I realised that a T4 template and an XML file would reduce my effort a lot. I embarked on learning all about T4 and within a day I was auto-generating over 7000 lines of useful code from far less than that.
I am still working on some nitty gritty details in that framework so I'd rather not blog about it in detail just yet. However, having become more familiar with T4 templates, it was much easier to spot other places they could add value. The next opportunity arose when working on another project. One of the first things I tend to do when setting up a development project is set up the versioning. I split common attributes shared by every assembly in my solution into a different file and then link to it in each project.
[assembly: System.Reflection.AssemblyCompany("SomewhatAbstract.com")] [assembly: System.Reflection.AssemblyProduct("DoobreyFlap")] [assembly: System.Reflection.AssemblyCopyright("Copyright © 2012 SomewhatAbstract.com")] [assembly: System.Reflection.AssemblyConfiguration("alpha")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Patch // Revision // [assembly: System.Reflection.AssemblyVersion("1.0.0.101")] [assembly: System.Reflection.AssemblyFileVersion("1.0.0.101")]
This single file is then updated by a script (MSBuild step or pre-build BAT or PS file) based on information from my source control provider of choice. You may wonder where T4 fits in here. Well, it doesn't, yet.
The next step in a development project is to consider deployment. I use Windows Installer XML (aka WIX) for my installers, an open source installation framework that uses XML files to describe the installer. Just as with my source code, I split the version information into its own file as this simplifies update of that information and allows me to share it across different installations. I also update some of this information using a script.
<?xml version="1.0" encoding="utf-8"?> <!-- These values should be updated when a new release is made. This ensures that the new version can upgrade the old. --> <!-- This is the product version. Note: only the first 3 tokens are used (i.e. 1.0.0.0 will be ignored). --> <?define ProductVersion=1.0.0?> <?define ProductVersionText="1.0.0.101"?> <?define ProductState="alpha"?> <Include />
So, now I have two version files. My scripts update these to include revision information from my source control provider (if you are familiar with semantic versioning, this goes in the build version, though I admit, in my examples I am not using SemVer). However, when doing a new major, minor or patch update, I have to manually edit each of these files to change the major, minor or patch version. On more than one occasion, I've updated one file and not the other. This only gets more complicated if there are additional files that need versioning (maybe there's also VB projects or some markdown for a release notice). This is where T4 templates comes in handy.
1 file, many files
Now, some of you may think that this is where my plan falls down. Can't T4 templates only output one file? How can one file be suitable for C#, VB and XML all at the same time? The answer is, it can't. At least not without a little help.
As you can execute any code in a T4 template, you could just write out a file using a FileStream
or something similar. However, while researching T4 templates for my unit conversion framework and realising that 7000 lines of code and tens of different types in a single file might be a tad unmanageable in some circumstances, I discovered a rather handy T4 mix-in that permits me to output multiple files from a single template using a simple syntax.
Using Damien Guard's Manager.ttinclude
file, I created a T4 template and outputted my C# and WIX files. Now, updating the version just means updating the template. This could easily be extended so that the information is read from some other file (such as an XML or JSON file) rather than hard-coded in the template. In addition, the revision information that I extract from source control via script could be extracted via the template itself, if I so choose.
<#@ template debug="true" hostSpecific="true" #><#@ output extension=".cs" #><#@ include file="Manager.ttinclude" #><# // Create the file manager so we can output multiple files. #><# var manager = Manager.Create(this.Host, this.GenerationEnvironment); #><# #><# // Setup the version information. #><# var version = new Version(1, 0, 0, 101); #><# var configuration = "alpha"; #><# var company = "SomwhatAbstract.com"; #><# var product = "DoobreyFlap"; #><# var copyright = "Copyright © 2012 SomewhatAbstract.com"; #><# #><# // Output the C# versioning #><# // #><# // VersionInfo.g.cs #><# // #> [assembly: System.Reflection.AssemblyCompany(<#= company #>)] [assembly: System.Reflection.AssemblyProduct(<#= product #>)] [assembly: System.Reflection.AssemblyCopyright(<#= copyright #>)] [assembly: System.Reflection.AssemblyConfiguration(<#= configuration #>)] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Patch // Revision // [assembly: System.Reflection.AssemblyVersion(<#= version.ToString() #>)] [assembly: System.Reflection.AssemblyFileVersion(<#= version.ToString() #>)] <# #><# #><# #><# // Output the WIX versioning #><# // #><# // VersionInfo.g.wxi #><# // #><# manager.StartNewFile("VersionInfo.g.wxi"); #> <?xml version="1.0" encoding="utf-8"?> <!-- These values should be updated when a new release is made. This ensures that the new version can upgrade the old. --> <!-- This is the product version. Note: only the first 3 tokens are used (i.e. 1.0.0.0 will be ignored). --> <?define ProductVersion=<#= version.ToString(3) #>?> <?define ProductVersionText="<#= version.ToString() #>"?> <?define ProductState="<#= configuration #>"?> <Include /> <# manager.EndBlock(); #><# manager.Process(true); #><#+ #>
This template will output two files: VersionInfo.g.cs
and VersionInfo.g.wxi
. Note that the C# file is named after the template whereas the WIX file is named on line 41. I recommend adding the g
suffix to make it easier to see that these are auto-generated files. I have this template in a special PreSolutionBuild project that sits at the start of the dependency chain for my solution. This keeps things nice and tidy in my solution.
Conclusion
As you can see, this is a very simple use for T4 templates, yet it solves a reasonably common issue. In the past I may have addressed this issue of making sure the version files are up-to-date by adding to the release processes or creating a bespoke tool. Personally, I prefer the elegance of the T4-based approach and I hope that others find it just as useful as I do.
For more information on T4 templates, I found the following resources exceedingly useful:
- Code Generation and T4 templates, MSDN
- Oleg Sych
- Multiple outputs from T4 made easy – revisited, Damien Guard