Every developer has to have an idea of versioning his products. If you work with Visual Studio you have the
Assembly Information in the project properties dialog, to enter it manually everytime you want to release a new version:
The four fields are: MAJOR, MINOR, BUILD, REVISION.
But seriously … who does that? I guess 99% of all C# developers are entering the
AssemblyInfo.cs and enter the famous 2 asterisks into the version declaration of BUILD and REVISION, to let Visual Studio do the incrementation job:
But this is not the end of the possibilities … Let’s do it more meaningful, with some goodies and still automatic…
A build with an increased MAJOR version number means, that there are significant changes in the product, even breaking changes. This always should be set manually.
Also the MINOR. It stands for significant functional extensions of the product.
How does Visual Studio calculate BUILD and REVISION?
When specifying a version, you have to at least specify major. If you specify major and minor, you can specify an asterisk for build. This will cause build to be equal to the number of days since January 1, 2000 local time, and for revision to be equal to the number of seconds since midnight local time, divided by 2.
But, the BUILD number should explain, how often a software with a particular MAJOR.MINOR has been build, due to minor changes and bug fixes.
The “Asterisk” REVISION number is a little weird, but at least with the BUILD number unique. But it says nothing. Better to pick up the idea of a date calculated, unique number, but not an arbitrary date … let’s take the date the project has started.
For example: 18.104.22.168 … reads version 1.2 with 16 builds on the 158’th day after the project has started.
A Text Template (.tt) has Directives (how the template is processed), Text blocks (text copied to the output) and Control blocks (program code).
For our versioning template, we start with this in a new file named
<#@ template hostspecific="true" language="C#" #>
// This code was generated by a tool. Any changes made manually will be lost
On saving the TT file, a new CS file with the same name will be created automatically and you got an error like this:
Th error occurs, because we have now two
AssemblyFileVersion attributes in our project. We need to comment out the original in
It makes sense to store all needed files for the new versioning system in a new root folder of the project, named AssemblyVersion, starting with the
AssemblyVersion.tt, because there will be more files later on.
As we replaced the original version attributes in the project with those from our generated
AssemblyVersion.cs, we cannot control the MAJOR and MINOR version number via the project property dialog any longer. We need a new approach on that, which can be edited easily and processed automatically.
This new JSON file has two main items:
initialDate- the date the project has started, to calculate the REVISION later on
versions- a list with all different MAJOR/MINOR versions we have done so far, with at least one without a release date … the one with the highest
remarks attribute of a list item holds some information about the changes in a new version. Together with
releaseDate, useful for a possible release history, shown in the product itself.
T4 runs in its own app domain, therefore it can use built-in libraries as
System.IO, but not third-party libraries like
We could reference those libraries from the projects package folder via the absolute path (if we use it in our product), but when we are running a NuGet update, the reference will break.
It is advisable to store such libraries directly in a fixed folder, like AssemblyVersion\Libraries. They won’t have any impact to our product, because the are only used while design time.
To process the new
AssemblyVersion.json in the template, we need some new directives for referencing the needed libraries and the import of the appropriate namepaces:
<#@ assembly name="System.Core" #>
Via the use of the T4 variable
$(SolutionDir), we can point to our copy of Newtonsoft JSON.
Now we can read and convert the JSON into an anonymous object and get the highest values of MAJOR and MINOR:
In order to get the version number for BUILD, we need a method to count and store every build that has been run, separated by the MAJOR/MINOR versions. This is a job for a Post-build event, which can be configured in the project properties dialog. The event uses shell commands as they are used on the command line.
What the commands should do: Write a new line with the current date and time in a log file, named after the MAJOR/MINOR version and stored in the folder AssemblyVersion\BuildLogs.
Shell commands for build events are supporting built-in variables, so called ‘macros’, like
$(ProjectDir) (which returns the project directory path), but there is no such macro for the current version number. We have to introduce it via extending the project with a new build target.
Unload the project in Visual Studio for editing the CSPROJ (or VBPROJ) file of your product manually and write the following definition just before the end-tag:
After reloading the project in Visual Studio, we can use
@(VersionNumber) in our commands.
The event build editor is not very comfortable, so we create the batch file
CreateBuildLog.bat in our AssemblyVersion folder and use this as the post build event command.
The BuildLog folder must exist, before running the following command the first time!
"$(ProjectDir)\AssemblyVersion\CreateBuildLog.bat" "$(ProjectDir)" @(VersionNumber)
As we have now the BuildLogs, we can use them in the template:
Very important is to create the log file, if it doesn’t exists! Otherwise the build will always fail, because the version attributes can’t be created.
At least we have to set the REVISION number, by calculating the difference between the current date and the
initialDate, which we have previously read from the
The last hurdle is to run the text transformation every time you build your product. Until now it runs only on saving the
A great helper on that was Thomas Levesque’s post “Transform T4 templates as part of the build, and pass variables from the project”, where he describes every difficulty to reach this goal.
To make it short: We have to edit the CSPROJ file again, to introduce TextTemplating to MSBuild.
First we need following near the beginning of the projects XML:
Secondly add the IMPORT of the TextTemplating target AFTER the CSharp target:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
If you build your product now, a new build log is created and the version numbers BUILD and REVISION are automatically increased.
The project where I implemented this versioning first is HexoCommander. Feel free to download the code and see how the new versioning mechanism works.