Versioning in .NET core

Posted by

One of the best practices we’ve found over the years is to include versioning information right on our website. As we have a lot of projects to balance, easily viewing the .Net version, underlying operating system, and our assembly information. This all helps with troubleshooting and to ensure we are running the version we expect, without loading the Azure Portal or our code.

To start today, we are going to extract some key version information with C# to display in our application, before diving into the details of customizing that version.

Extracting operating system information

To get the operating system our application is running on, we just need to call the System.Runtime namespace for the “OSDescription”.

string osPlatform = System.Runtime.InteropServices.RuntimeInformation.OSDescription;

We run our application in Azure on a Windows web server, so it displays “Microsoft Windows 10.0.14393”, but this will display other operating systems too. Note that this namespace is also useful if you need to branch your code for specific operating systems, for example:

if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
  // Do something Linux related
}

Extracting the .Net Core version

To get the .Net Core version our application is targeting, we use the System.Reflection namespace again to get the entry assembly information. Note that you need a “using System.Reflection” on the top of the code file you are using this for, as prefixing it to the Assembly name does not work. In our case, this displays “.NETCoreApp,Version=v2.2”. We confirmed this works with the .Net 3.0 preview that is floating around too.

string aspDotnetVersion = Assembly
    .GetEntryAssembly()?
    .GetCustomAttribute<System.Runtime.Versioning.TargetFrameworkAttribute&gt;()?
    .FrameworkName;

Extracting our application assembly version

Our application assembly version is a little simpler. In this example, this will return the version number for the project this is run in:

string appVersion = Assembly.GetEntryAssembly().GetCustomAttribute().Version;

Extracting our application assembly build time

Finally, as a bonus, we are getting the entry assembly last modification date – this is when the assembly was built. While getting a version number is great, seeing a last modified date provides a lot more context and is easier for a human to comprehend and confirm the deployment was successful. Unfortunately, this is a slightly messy calculation, as we have to count the number of seconds from the 1st Jan 1970. We then apply a local timezone adjustment to adjust for UTC.

const int peHeaderOffset = 60;
const int linkerTimestampOffset = 8;
byte[] bytes = new byte[2048];
using (FileStream file = new FileStream(Assembly.GetEntryAssembly().Location, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
    file.Read(bytes, 0, bytes.Length);
}
Int32 headerPos = BitConverter.ToInt32(bytes, peHeaderOffset);
Int32 secondsSince1970 = BitConverter.ToInt32(bytes, headerPos + linkerTimestampOffset);
DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime dateTimeUTC = dt.AddSeconds(secondsSince1970);
DateTime localTime = TimeZoneInfo.ConvertTimeFromUtc(dateTimeUTC, TimeZoneInfo.Local);
string applicationLastBuildTime = localTime.ToString("dd-MMM-yyyy hh:mm:sstt") + " " + TimeZoneInfo.Local.Id;

We also need to make a minor change to our csproj file. We edit it and add in the following code. The reason this is required it documented here, but it is related to a change in Visual Studio 15.4.

  <PropertyGroup&gt;
      ...
      <Deterministic&gt;false</Deterministic&gt;
  </PropertyGroup&gt;

What does the result look like? On our about page, we add all of the code above, and now see this useful information whenever we open the page.

Auto-incrementing .Net core assembly versions

This is helpful, but wouldn’t it be more helpful if the minor versions of the build number increased automatically? In .NET Framework, you only had to update the version with a “*”, for example: 1.31.*.*. In .NET Core, this doesn’t work anymore. We did quite a bit of research, and in the end elected to solve this problem with a bit of PowerShell that we will run in the YAML build. The code reads from the project csfile name, splits the version into the 4 parts, appending the first 2 numbers (the major and minor version) , and then calculating the last two pieces. The third number in the version is the number of days since 1-Jan-2000, and the fourth/last version number is the number of seconds since midnight divided by 2. The last step of the script is to assign the variable back to the Azure DevOps pipeline variables, (we covered this in more detail in a previous post).

Write-Host "Generating Build Number"

#Get the version from the csproj file
$xml = [Xml] (Get-Content SamLearnsAzure/SamLearnsAzure.Web/SamLearnsAzure.Web.csproj)
$initialVersion = [Version] $xml.Project.PropertyGroup.Version
Write-Host "Initial Version: " $version
$spliteVersion = $initialVersion -Split "."

#Get the build number (number of days since January 1, 2000)
$baseDate = [datetime]"01/01/2000"
$currentDate = $(Get-Date)
$interval = (NEW-TIMESPAN -Start $baseDate -End $currentDate)
$buildNumber = $interval.Days

#Get the revision number (number seconds (divided by two) into the day on which the compilation was performed)
$StartDate=[datetime]::Today
$EndDate=(GET-DATE)
$revisionNumber = [math]::Round((New-TimeSpan -Start $StartDate -End $EndDate).TotalSeconds / 2,0)

#Final version number
$finalBuildVersion = "$($spliteVersion[0]).$($spliteVersion[1]).$($buildNumber).$($revisionNumber)"
Write-Host "Major.Minor,Build,Revision"
Write-Host "Final build number: " $finalBuildVersion

#Writing final version number back to Azure DevOps variable
Write-Host "##vso[task.setvariable variable=buildNumber]$finalBuildVersion"

Putting this all together, we also need to add a line to our dotnet publish command, to use the MSBuild command “-p:Version”, and then the variable name. Note that if use the version argument on dotnet build, a later dotnet command (e.g. publish) will overwrite the version – so this needs to be on the last dotnet command.

The result now shows a unique build version every time we build:

Note that in our research we spent some time looking at the popular MSBump, a Visual Studio add-on that increments the version every time the project is compiled in Visual Studio. However, this doesn’t increase the version inside of Azure DevOps, which is a deal breaker for us. We need every Azure DevOps build to generate a unique version number.

Perhaps there could be a better way to version?

This last section, prompted a conversation about meaningful version numbers. For example, the number of seconds after midnight is not meaningful. Perhaps we would be better with some text describing the build, for example “1.31.0-RTM”, (where RTM is short for release to manufacturing). Another option could be tagging pull request deployments, so that the version shows as “1.31.0.PR456”. Yet another solution could be to use the date and time and format it for the version, e.g. “1.31.2019-Sep-09.08:34:23AM”. While we have elected to not do any of these options yet, this can be easily achieved using the VersionPrefix and VersionSuffix properties, with more details of how this works here.

Wrap up

We have covered a lot of minor details around versions and tagging our project with useful information to help identify the version, operating system and time it was deployed. All of this helps to identify unique versions when supporting our end users.

References

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s