Global Assembly Versioning Strategy & Development Workflows for .NET Assemblies

Version 1.0Over the past few weeks I've been working on a new project in our company which involved building a number of inter-dependent assemblies, "strongly naming" them and installing them into the Global Assembly Cache. Over the course of the project, I was forced to look at a number of issues related to assembly versions, solution organisation and the deployment of assesmblies in a developer environment.

So given that it's been a while since I wrote anything vaguely technical, I thought I'd document some of these issues down.

  • What version numbering strategy should we use?
  • How will we organise our Solution to make this easily manageable?
  • How will we manage these libraries during the deployment phase?
  • How will we circulate stable versions to developers during on-going development of other projects?
  • How will we release these libraries to customers?
[notice]TL;DR - Take a look at the VersioningDemo Solution on GitHub[/notice]

Assembly Version Attributes in .NET Assemblies

Before we start it's probably worth spending a little time on Assembly Versions in .NET. Microsoft .NET supports 3 different types of "Version Number" information on an Assembly. Descriptions below are taken from MSDN.

  1. Assembly Version; The assembly version number is part of an assembly's identity and plays a key part in binding to the assembly and in version policy. The default version policy for the runtime is that applications run only with the versions they were built and tested with. (Unless overridden in configuration).
  2. Assembly File Version; A specific version number for the Win32 file version resource. The Win32 file version is not required to be the same as the assembly's version number, buf if not supplied, the AssemblyVersionAttribute is used. It can be seen on the Version tab of the Windows file properties dialog.
  3. Assembly Informational Version; An additional version information for an assembly manifest. If the AssemblyInformationalVersionAttribute is not applied to an assembly, the version number specified by the Assembly Version Attribute attribute is used instead.

These 3 attributes can be set on an assembly in the $Project\Properties\AssemblyInfo.cs file

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.0.0.*")] //Auto Revision Number
[assembly: AssemblyVersion("1.0.*")]   //Auto Build & Revision Numbers

[assembly: AssemblyFileVersion("1.0.0.0")]

[assembly: AssemblyInformationalVersion("1.0.0.0")];

For the AssemblyVersionAttribute we have the option of replacing the standard {major}.{minor}.{build}.{revision} format, with either {major}.{minor}.{build}.* or {major}.{minor}.* which will cause the compiler to automatically generate the build and revision components. If we do choose to use the automatic build and revision numbers, the {build} number will default to the number of days since January 1st, 2012. The {revision} number will default to the number of seconds since midnight, divided by 2.

http://msdn.microsoft.com/en-us/library/system.reflection.assemblyversionattribute.aspx

Creating a Strong Name Key for your Assemblies 

In order to place a .NET Assembly into the GAC, the assembly must be "Strongly Named". A strong name is an identifer for the assembly comprising of  simple text name, version number, and culture information (if provided) and a public key and a digital signature. In order to strongly name your assembly we need to generate a Strong-Name Key. This can be done from inside Visual Studio, but this will make the key a part of that specific project. My personal preference is to generate the key externally using the sn.exe tool. This makes it a little easier to store the key else where and share it among other projects as we'll see below.

sn -k keyPair.snk

Solution Organisation

So lets start creating the solution. The sample solution is available here from GitHub which shows the final product. First we create 2 Class Library projects and make one a reference of the other. We also create a "Solution Items" folder for storing common items such as our Strong Name Key and common assembly info.

Solution Organization Solution Organization

We also update the output path for all assembly configurations to just bin\. This makes it easier to manage Debug/Release versions in post-build batch scripts as only one version can exist simultaneously.

Common bin\ Output Common bin\ Output

Common Strong Name Key File

Next we create our Strong Named Key. In order to use the same key on each of our assemblies we externally generate an snk file using the following command and place it in an windows explorer folder under the solution.

sn -k VersioningDemo.snk

This file is placed in the root solution folder in windows explorer and then dragged into the Solution Items Folder within the Solution. Finally, we add this snk file to each individual project as a "Linked" File.

Adding a Strong Named Key as a Linked File Adding a Strong Named Key as a Linked File

Once the strong named key is added as a linked file, we configure the assembly to be signed-on-build in the project properties screen.

Signing The Assembly Signing The Assembly

Common "GlobalAssemblyInfo" File

Each project comes with a default Assembly Info File under its property folder. In large solutions, however, maintaining all these different assembly info files can be unwieldy, especially if there are common attributes that need to be shared/synchronized between the different projects. We can achieve this by adding a shared "GlobalAssemblyInfo" file to the solution. Adding the file to each project is done in the same way as above for snk. We create a GlobalAssemblyInfo.cs in the solution root folder in Windows Explorer, add it to the Solution Items folder inside the SLN, and then add it to each individual project as a linked file. Once added, it can be dragged under the Properties folder to keep things organised.

Organised Solution Organised Solution

The purpose of having these two files is that we can now split out the different assembly attributes that are specific to our individual projects, from the ones that we want to keep common across the solution. In the sample solution the attributes have been divided over the two files in the following way.

Global Assembly Info

using System.Reflection;
#if DEBUG
[assembly: AssemblyConfiguration("Debug")]
#else
[assembly: AssemblyConfiguration("Release")]
#endif
[assembly: AssemblyCompany("My Company")]
[assembly: AssemblyCopyright("Copyright © My Company 2012")]
[assembly: AssemblyTrademark("My Trademark ™")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]

Project Assembly Info

using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("VersioningDemo.DependentLib")]
[assembly: AssemblyDescription(
           "The DependentLib Library of the VersioningDemo Solution")]
[assembly: AssemblyProduct("VersioningDemo.DependentLib")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("fc4e547c-1797-4381-a957-d024abbf2f26")]

Version Number Strategy

 

Phew! Getting there... so now that the solution is organised, we need to decide what version numbering scheme/strategy to use to version our Assemblies. What other considerations need to be taken? This is a very open ended subject and will most likely be dependent on your own company or situation. You should bear the following in mind.

  1. Try and stick to the {major}.{minor}.{something}.{something} versioning mechanism. It's not necessary to use build numbers and software revisions but following a general major/minor verison number pattern will help your team, sales people and customers alike recognise where on the product version ladder they are.
  2. Remember that changing the AssemblyVersion number will mean having to recompile/re-release every other application that references that assembly.
  3. Remember that changing the Assembly File Version number will not. This can freely change between releases.

In my company, we use a numbering scheme of {Year}.{Quarter}.{Month_Of_Quarter}.{Revision} as we only do quarterly stable builds of our core assemblies. In my own personal projects, I use a standard incrementing {major}.{minor} scheme with current {day_of_year_number * year} as the {build} portion of the Assembly and AssemblyFile version numbers. And the .Net standard of current second in the day divided by 2 for the revision. This is easily gotten using this quick powershell script.

PS C:\> [int32](((get-date).Year-2000)*366)+(Get-Date).DayOfYear
4781
PS C:\> [int32]((get-date)-(Get-Date).Date).TotalSeconds
62595
PS C:\>
# 4781 = ((2013-2000) * 366) + 23 = Wednesday January 23rd, 2013

The revision component of the AssemblyVersion is left out, but in the Assembly File Version it's set to the current Source Control ChangeSet Revision at the time of build. Finally the Informational Version is set to a more verbose description of the current software production version.

[assembly: AssemblyVersion("1.0.2736")]
[assembly: AssemblyFileVersion("1.0.2736.67")]
[assembly: AssemblyInformationalVersion("VersioningDemo v1.0.2736.67 RC1, Hotfix B")]

[notice]So why go to all this trouble?

Well this is a very powerful combination of information. When we lock down a release and roll it out, the AssemblyVersion is committed as part of the assembly strong name and once installed in the GAC, any applications compiled against this version are tied to that AssemblyVersion.

But imagine we find a bug. It doesn't change any interface contracts or methods but it needs to be fixed with a hotfix release. No problem, we can rebuild and re-release the assembly with the same AssemblyVersion, and the applications will continue to happily reference it. We can also bump the AssemblyFileVersion so we can see at a glance which specific version is in place at a particular deployment site. We can view the File Version and Informational Version through the Windows Explorer Properties window.[/notice]

Assembly Properties Assembly Properties

Development Workflow

Now that we have a stable build of our libs we have a few things to consider relating to our development process and developer workflow. Let's take each of the following, one at a time.

  • Since we intend to add these libs to the GAC, how can we build & debug them in that same context?
  • How will we distribute Stable Versions to other Developers?
  • How will we keep other developers in Sync?

Auto-GAC'ing Assemblies

Visual Studio comes with an SDK Tool called GACUtil for adding Assemblies to the Global Assembly Cache. It's important to note that GACUtil is not a redistributable application and should NOT be used for adding assemblies to the GAC in a production environment, but in "Developer-land" we are free to leverage it. After each build we can automatically place each assembly in the global assembly cache using a Post-Build Command on the Project. (Project -> Properties -> Build Events -> Post-Build Event)

"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe" /i "$(TargetPath)"

Even if we install the assemblies from the "Stable Build" Repository (which we'll talk about next), each subsequent build of the VersioningDemo Solution on our own machine will supersede those references and replace them in our GAC (so long as we haven't updated our AssemblyVersion Number).

The "Stable Builds" Repository

Once a valid "cut" of the assemblies has been taken for distribution (i.e. a stable branch or tag or whatever the in-house process might be), we'll complete a release build of our Application and then move our assemblies to a "Stable Builds" folder within our Source Control System. The solution contains a folder called "Stable" under the root of the solution folder. Beneath this, a directory is created for each stable build and named with that build's assembly version number.

Stable Folder Structure Stable Folder Structure

To help manage this we can use the following CopyToStable.bat batch file which pushes the latest compiled assemblies from the \bin\ directory to a specified stable version folder.

@ECHO OFF
SET /P GACVersion=Enter the Assembly Version Number for this cut: 
IF '%GACVersion%'=='' (
	ECHO You must enter a version number.
	PAUSE
	GOTO:EOF
)

SET DestDir="%CD%\Stable\%GACVersion%"
IF NOT EXIST %DestDir% MKDIR %DestDir%
DEL /S /F /Q %DestDir%*.*

COPY %CD%\VersioningDemo.Core\bin\*.dll %DestDir%
COPY %CD%\VersioningDemo.Core\bin\*.pdb %DestDir%
COPY %CD%\VersioningDemo.Core\bin\*.xml %DestDir%
COPY %CD%\VersioningDemo.DependentLib\bin\*.dll %DestDir%
COPY %CD%\VersioningDemo.DependentLib\bin\*.pdb %DestDir%
COPY %CD%\VersioningDemo.DependentLib\bin\*.xml %DestDir%

ECHO Done!
PAUSE

Keeping Other Developers "In-Sync" via SVN

Finally we need to circulate these libraries to the other developers in the team. This is accomplished by checking the Stable folder into SVN. We have a developer team svn script which we use to pull the latest head revision from trunk each morning (or the relevant branches that a developer might be working on). At the bottom of this script we've included some additional batch commands to automatically iterate all the subfolders beneath each Stable Build Directory and automatically re-install the assemblies to the GAC. Considerations:

  • Since "Stable" is committed in the same tree structure as the solution itself, branching the solution also branches the Stable Libs.
  • Running the "GetTrunk" svn script will auto check-out and GACUtil the latest versions of the Stable Libs from Trunk
  • Running one of the "GetBranch" svn scripts will auto check-out and GACUtil the latest versions of that branch superseding what was previously installed to the GAC.
  • Opening and re-compiling the GAC Assembly Solution locally will automatically add those newly compiled assemblies to the GAC, superseding what was previously installed.
@echo off
echo Updating GAC. Please wait...
echo GAC Update on %date% at %time% > GACLog.txt
echo Stopping IIS for GAC registration...
iisreset /STOP > nul
dir /B /S Stable\*.dll > AssemblyList.txt
echo ---------------- AssemblyList.txt ------------- >> GACLog.txt
"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe" /il AssemblyList.txt /f >> GACLog.txt
echo ---------------- AssemblyList.txt --------------- >> GACLog.txt
echo Deleting contents of download cache...
"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\gacutil.exe" /cdl >> GACLog.txt /nologo
del AssemblyList.txt 2>> GACLog.txt
echo Restarting IIS...
iisreset /START > nul
echo Done.
echo Please see 'GACLog.txt' for details of GAC Install.
pause

So there you have it. My current solution for managing and versioning Strong-named, GAC'd assemblies in a Development Workflow. If anyone has any feedback, recommendations or things they do differently, I'd love to hear about them.

~Eoin Campbell

Eoin Campbell

Eoin Campbell
Dad, Husband, Coder, Architect, Nerd, Runner, Photographer, Gamer. I work primarily on the Microsoft .NET & Azure Stack for ChannelSight

CPU Spikes in Azure App Services

Working with Azure App Services and plans which have different CPU utilization profiles Continue reading

Building BuyIrish.com

Published on November 05, 2020

Data Partitioning Strategy in Cosmos DB

Published on June 05, 2018