Introduction
The last five posts (1, 2, 3, 4, 5) were all about fun stuff with Arduino. Now it’s time for something more mundane. ;) In this post, I will show you how to create TeamCity build that automatically sets version information in all assemblies produced by ASP.NET application. It's nothing new but I hope to give you some useful background information and note a few gotchas you may face...
The complete code of the sample application is available in this GitHub repository.
TeamCity has a build feature called AssemblyInfo patcher that makes setting assembly version easy... This feature is usable on any type of .NET project because it works by updating AssemblyInfo
files. Content of such files is used to create version information that .NET Framework uses for picking up correct version of referenced assemblies. Version data is also shown in Windows file properties... Here's a part of AssemblyInfo.cs file which is automatically added by Visual Studio when you create a new project:
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
It contains two attributes: AssemblyVersion
and AssemblyFileVersion
along with a comment that describes numbering pattern recommended by Microsoft. We will also use another attribute: AssemblyInformationalVersion
which is not added by default. AssemblyVersion
sets version number that is recognized by .NET for dependency resolution. AssemblyFileVersion
is used for file version as seen by Windows and AssemblyInformationalVersion
is meant more for human consumption as it can contain string
s (we will make use of it for holding Git commit hash)... Detailed description of the meaning of these attributes is outside the scope of this post but check this great SO answer if you want to know more.
Sample Application
My test application was created in Visual Studio Community 2013 by using ASP.NET Web Application / MVC project template (C#/.NET 4.5). Two additional projects of Class Library type were added. Here’s how the full solution looks:
Home/Index.cshtml view generated by VS was modified to present version information pulled from three .NET assemblies that are produced by the solution (one is for main web app project and two others are for class libraries). Such div
was added to the view:
<div class="row text-primary">
<div class="col-md-12">
<dl>
<dt>Core assembly info:</dt>
<dd>@ViewBag.CoreAssemblyInfo</dd>
<dt>DataAccess assembly:</dt>
<dd>@ViewBag.DataAccessAssemblyInfo</dd>
<dt>Web assembly info:</dt>
<dd>@ViewBag.WebAssemblyInfo</dd>
</dl>
</div>
</div>
You can see some Bootstrap classes there since nowadays Visual Studio templates use Bootstrap framework for styling...
This is how rendered view looks before TeamCity processes AssemblyInfo.cs files:
And here's how version info looks after version attributes are modified by build feature:
If you wonder how the view gets version info, here's HomeController.Index
action method:
public ActionResult Index()
{
ViewBag.CoreAssemblyInfo = SomeCoreClass.GetAssemblyInfo();
ViewBag.DataAccessAssemblyInfo = SomeDataAccessClass.GetAssemblyInfo();
Assembly assembly = Assembly.GetExecutingAssembly();
string webAsseblyInfo = string.Format
("Full Name = \"{0}\"; Informational Version = \"{1}\"",
assembly.FullName,
FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion);
ViewBag.WebAssemblyInfo = webAsseblyInfo;
return View();
}
You can see how the most important assembly version number (the one used by .NET and designated by AssemblyVersion
attribute) is a part of assembly's FullName
. Informational version (the one that can have string
s) is taken with the help of FileVersionInfo
class. You can get the number form AssemblyFileVersion
attribute too - just check all the interesting stuff that GetVersionInfo
method returns... The same kind of code is used in GetAssemblyInfo
methods in SomeCoreClass
and SomeDataAccessClass
.
Ok, so we have our test application - the full code is here. Note: I’ve pushed all used Nuget packages to the repository – that takes some space in the repo and might be against the recommended way of using Git but it makes TeamCity setup easier. If packages folder is not committed, you can expect this type of error during build:
[Csc] App_Start\BundleConfig.cs(2, 18): error CS0234:
The type or namespace name 'Optimization' does not exist in the namespace 'System.Web'
(are you missing an assembly reference?)
To solve it, you would have to restore Nuget packages during build (here’s some info on how to do it).
Teamcity Configuration
Now, it's time for building the server config! I assume that you have some working knowledge about setting TeamCity build for .NET application, so I will discuss only the steps relevant to versioning. I’ve used TeamCity 9.1.3 but don't worry if you have a bit older TC (AssemblyInfo
patcher feature exists for a while). I used TC to build code from Git repository checkout on my local drive...
Before setting up AssemblyInfo
patcher, add two new build parameters: Minor
and Major
. These are meant to represent two initial segments of version number and should be set manually - it's your (technical/marketing?) decision whether to name your next version 1.9 or 2.0, right?
The next step is to add AssemblyInfo
patcher build feature:
And set its properties:
I've decided to use such settings:
AssemblyVersion: %Major%.%Minor%.%build.number%
AssemblyFileVersion: %Major%.%Minor%.%build.number%
AssemblyInformationalVersion: %Major%.%Minor%.%build.number%.%build.vcs.number%
You can see that our Major
and Minor
parameters are used. You can also see the use of TeamCity built-in parameter named build.number
. Last attribute contains another TeamCity param: build.vcs.number
. It gets version control revision id. I'm using Git so this is a long alphanumerical SHA-1 hash. It means that it cannot be used in setting AssemblyVersion
attribute. If you try to do so, you will get an error like this:
[Csc] Properties\AssemblyInfo.cs(35, 12): error CS0647:
Error emitting 'System.Reflection.AssemblyVersionAttribute'
attribute -- 'The version specified
'2.1.13.536ea0163412325ab7962957ce1cec777799d587' is invalid'
If you try to use it for AssemblyFileVersion
, you can expect a warning:
[Csc] CSC warning CS1607: Assembly generation -- The version
'2.1.12.536ea0163412325ab7962957ce1cec777799d587' specified for the 'file version'
is not in the normal 'major.minor.build.revision' format
But you can safely use it in AssemblyInformationalVersion
as .NET doesn't care if you put letters there... Note: If you work with SVN instead of Git, you are lucky because value returned for build.vcs.number
is an integer and can be used in all three version-related attributes. If you really need to set revision in AssemblyVersion
while using Git, you might need to add a custom build step for creating integer id... Let's keep it simple here and leave the last part of version number intact (as 0)...
Once you have AssemblyInfo
patcher feature configured and you run the build, you can expect such entries in the build log:
[22:46:02]Step 1/1: Visual Studio (sln) (2s)
[22:46:02][Step 1/1] Update assembly versions:
Scanning checkout directory for assembly information related files to update version to 2.1.14
[22:46:02][Update assembly versions] Updating assembly version in
C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\AssemblyInfoTest\Properties\AssemblyInfo.cs
[22:46:02][Update assembly versions] Updating assembly version in
C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\Core\Properties\AssemblyInfo.cs
[22:46:02][Update assembly versions] Updating assembly version in
C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\DataAccess\Properties\AssemblyInfo.cs
If all went OK, your log should also contain something like this:
[22:46:05]Reverting patched assembly versions
[22:46:05][Reverting patched assembly versions] Restoring
C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\AssemblyInfoTest\Properties\AssemblyInfo.cs
[22:46:05][Reverting patched assembly versions] Restoring
C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\Core\Properties\AssemblyInfo.cs
[22:46:05][Reverting patched assembly versions] Restoring
C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\DataAccess\Properties\AssemblyInfo.cs
Don't worry, reverting takes place only in build agent work files. The build artifacts contain properly versioned assemblies. You've seen a proof of that rendered on HTML page, you can also check DLL files properties:
Properties window shows version set by AssemblyFileVersion
and AssemblyInformationalVersion
. I have Polish Windows so the properties are labeled Wersja pliku
(it means File version) and Wersja produktu
(it means Product version).
Keep in mind that AssemblyInfo
patcher will not work if version attribute has non-standard format (or AssemblyInfo
files are in unusual locations).
Let's say you have something like this (because you keep information about your product in static
class constants):
[assembly: AssemblyVersion(ProductInfo.Version)]
You can expect such warning during build:
[Update assembly versions] Assembly info version was specified, but couldn't be patched in file
C:\TeamCity\buildAgent\work\8c2a410f7087e36b\.NET\AssemblyInfoTest\AssemblyInfoTest\Properties\AssemblyInfo.cs.
Is necessary attribute missing?
Summary
And that's all! We have a TeamCity build that sets version information in ASP.NET MVC application assemblies. :)
If somebody will be interested, I can write a little supplement to this post in which I will describe how to add version info into zip package (artifact) and how to display it on Team City UI...