Introduction
I frequently need to set a semantic versioning version number when building ASP.NET Core API projects, then read or report this at runtime.
For example, if I build a minimal API with its semver version set to 1.0.2-beta, this would be reported by a /version endpoint exposed by the API, as shown in the screenshot below from Hoppscotch (this is a Postman-like tool with the convenience that it runs the browser):
Checking that the version reported from HTTP services (web apps ,APIs) is correct is a crucial part of my CD pipelines and is one of the (smoke) tests I use to determine if a deployment succeeded.
In this post I'll show you how to stamp a semver version number on a .NET assembly - an .exe or .dll compiled using a .NET compiler - during build, then read it at run time.
So many version numbers
As Andrew Lock's article on .NET versioning explains, a .NET assembly has not one but several different version numbers:
AssemblyVersion: This is a four part version number, e.g.
1.0.2.0
that is used when loading linked assemblies at run time.FileVersion: This is the version number reported for a
.dll
file in Windows UI when you right click the assembly and select Properties.InformationalVersion: Yet another version number and, like FileVersion, can be seen in in Windows if you right-click the assembly and select Properties. This can contains strings and not only numbers and dots that AssemblyVersion and FileVersion are constrained to.
PackageVersion: If the project is pacakge into a Nuget package, this would be the version number of the package that would be produced.
All of these version numbers are emitted into the assembly as metadata and you can see them if you inspect the assembly with JetBrains dotPeek (free) or Red gate Reflector (not free) or similar.
FileVersion and InformationalVersion can also be seen in Details tab of the dialog that appears when you right-click the assembly file in Windows file explorer and select Properties:
In the screenshot above, Product version is the caption for InformationalVersion whereas File version is the caption for FIleVersion.
Of the four types of version numbers described above, only the first three apply to any assembly (i.e. whether or not the assembly is part of a Nuget package).
Of those three, AssemblyVersion alsways adds a 0
in the fourth place if you try to set a semver version which only has three numbers (plus an optional prerelease suffix). For example if you try to set a semver version of 1.0.2-beta
during build and then read the AssemblyVersion value at run time in the assembly, it would be 1.0.2.0
.
FileVersion does the same, as shown in the screenshot above.
InformationalVersion is the only version number which would get set exactly to the server version we set during build (as screenshot above shows).
Therefore, InformationalVersion is the version that should be read at run time.
Set a SemVer version number
-
In a
<PropertyGroup>
element in the projectscsproj
file, add element<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
:
<PropertyGroup> ... <IncludeSourceRevisionInInformationalVersion> false </IncludeSourceRevisionInInformationalVersion> </PropertyGroup>
As described in this issue, this ensures that InformationalVersion is set exactly to the semver version number we specifiy and does not get a
+<hash code>
tacked on at the end. -
Pass the version number as value of
Version
parameter ofdotnet build
e.g.:
dotnet build --configuration Release /p:Version=1.0.2-beta
This would set InformationalVersion in the compiled assembly (.exe or .dll file) to
1.0.2-beta
.Note that you can instead set MS Build property
<Version>1.2.3-beta</Version>
in a<PropertyGroup>
element in the csproj file.However passing a value for
Version
parameter to dotnet build is simpler because the csproj file does not need to be modified every time the version number is incremented. This is helpful in CD pipelines. Also, by default, csproj files do not have any property related to versioning.
Reading an assemblys SemVer version at run time
Code that reads InfromationalVersion at run time is as follows:
string? version = Assembly.GetEntryAssembly()?.
GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.
InformationalVersion;
In my minimal APIs, to add a /version
endpoint as I showed in Inroduction section above, I place the above snippet in Program.cs
, before builder.Build()
is called, then add the following snippet immediately after:
//this object of an anonymous type will
//be serialised as JSON in response body
//when returned by a handler
var objVersion = new { Version = version ?? "" };
//OTHER CODE
//var app = builder.Build()
After builder.Build()
is called, I create the handler for /version
endpoint:
app.MapGet("/version", () => objVersion);
Now when I run the API project and call the /version
endpoint, I get the version number back in a JSON object in HTTP response body:
{
"version": "1.0.2-beta"
}