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>()? .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> … <Deterministic>false</Deterministic> </PropertyGroup>
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
- Setting .NET Core version: https://stackoverflow.com/questions/43274254/setting-the-version-number-for-net-core-projects-csproj-not-json-projects
- Getting .NET Core version: https://weblog.west-wind.com/posts/2018/Apr/12/Getting-the-NET-Core-Runtime-Version-in-a-Running-Application
- https://edi.wang/post/2018/9/27/get-app-version-net-core
- Getting the build date https://stackoverflow.com/questions/1600962/displaying-the-build-date/11053211
- Making sense of the build numbers in .NET: https://intellitect.com/making-sense-of-assemblyversion-numbers/
- dotnet restore wipes out my version number? https://stackoverflow.com/questions/54244642/tfs2017-dotnet-build-not-overriding-version-number
- MBBump: https://janaks.com.np/setup-auto-versioning-in-net-core-application-visual-studio-2017/
- Featured image: https://inedo.com/training/videos/images/universal-essentials/semver.png
is this safe? how about adding version number in CI or Build server, what if there are multiple developers working together on the same code base, is there a risk that the latest commit will have a lower version e.g. the latest version is 2.1.1
but the latest commit due to the last push the commits is 2.0.0
therefore the version id is now override, please advise, thanks!
LikeLike
Why is it not safe? You need to manage the version somewhere. This merely takes the major/minor version numbers from the code and appends a unique identifier, so that every build has a unique version.
LikeLike
Thanks, mate. Your note on the Versioning override on the publish command was really helpful! Been tearing out my hair why the generated artifacts from the pipelines still had the default version number. The official MS doc on publish doesn’t even mention the “Version” parameter, only VersionSuffix.
LikeLiked by 1 person
The docs were exactly why I wrote this – after tearing out my own hair for days. haha. Glad this helped! 🙂
LikeLike