Auto Versioning in Visual Studio 2017 (.NET Core)

I have spent the better part of a few hours trying to find a way to auto-increment versions in a .NETCoreApp 1.1 (Visual Studio 2017).

I know the the AssemblyInfo.cs is being created dynamically in the folder: obj/Debug/netcoreapp1.1/

It does not accept the old method of: [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.*")]

If I set the project to package I can set versions there but this seems to be used to build the AssemblyInfo.cs file.

My question is, has anyone figured out how to control version in .NET Core (or .NETStandard for that matter) projects.


Solution 1:

Add <Deterministic>False</Deterministic> inside a <PropertyGroup> section  of .csproj

The workaround to make AssemblyVersion * working is described in “Confusing error message for wildcard in [AssemblyVersion] on .Net Core #22660”

Wildcards are only allowed if the build is not deterministic, which is the default for .Net Core projects. Adding <Deterministic>False</Deterministic> to csproj fixes the issue.

The reasons why .Net Core Developers consider Deterministic Builds beneficial described in http://blog.paranoidcoding.com/2016/04/05/deterministic-builds-in-roslyn.html and Compilers should be deterministic: same inputs generate same outputs #372

However if you are using TeamCity, TFS or other CI/CD tool, it's probably better to keep the version number controlled and incremented by them and pass to build as a parameter (as it was suggested in other answers) , e.g.

msbuild /t:build /p:Version=YourVersionNumber /p:AssemblyVersion=YourVersionNumber

Package number for NuGet packages

msbuild /t:pack /p:Version=YourVersionNumber   

Solution 2:

If you're using Visual Studio Team Services/TFS or some other CI build process to have versioning built-in, you can utilize msbuild's Condition attribute, for example:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <Version Condition=" '$(BUILD_BUILDNUMBER)' == '' ">0.0.1-local</Version>
    <Version Condition=" '$(BUILD_BUILDNUMBER)' != '' ">$(BUILD_BUILDNUMBER)</Version>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
    <PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="1.1.2" />
  </ItemGroup>

</Project>

This will tell the .NET Core compiler to use whatever is in the BUILD_BUILDNUMBER environment variable if it's present, or fallback to 0.0.1-local if you're doing a build on your local machine.

Solution 3:

I have been looking for a version incrementer for a .NET Core app in VS2017 using the csproj configuration format.

I found a project called dotnet bump that worked for the project.json format but struggled to find a solution for the .csproj format. The writer of dotnet bump actually came up with the solution for the .csproj format and it is called MSBump.

There is a project on GitHub for it at:

https://github.com/BalassaMarton/MSBump

where you can see the code and it's available on NuGet too. Just search for MSBump on Nuget.

Solution 4:

You can use a MSBuild property function to set the version suffix based on current date:

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
  <VersionSuffix>pre$([System.DateTime]::UtcNow.ToString(yyyyMMdd-HHmm))</VersionSuffix>
</PropertyGroup>

This will output a package with a name like: PackageName.1.0.0-pre20180807-1711.nupkg.

More details about MSBuild property functions: https://docs.microsoft.com/en-us/visualstudio/msbuild/property-functions

The Version is formed from the combination of VersionPrefix and VersionSuffix, or if VersionSuffix is blank, VersionPrefix only.

<PropertyGroup>
  <VersionPrefix>1.0.0</VersionPrefix>
</PropertyGroup>

Solution 5:

I came up with a solution that worked almost the same as old AssemblyVersion attribute with star (*) - AssemblyVersion("1.0.*")

Values for AssemblyVersion and AssemblyFileVersion is in MSBuild project .csproj file (not in AssemblyInfo.cs) as property FileVersion (generates AssemblyFileVersionAttribute) and AssemblyVersion (generates AssemblyVersionAttribute). In MSBuild process we use our custom MSBuild task to generate version numbers and then we override values of these FileVersion and AssemblyVersion properties with new values from task.

So first, we create our custom MSBuild task GetCurrentBuildVersion:

public class GetCurrentBuildVersion : Task
{
    [Output]
    public string Version { get; set; }
 
    public string BaseVersion { get; set; }
 
    public override bool Execute()
    {
        var originalVersion = System.Version.Parse(this.BaseVersion ?? "1.0.0");
 
        this.Version = GetCurrentBuildVersionString(originalVersion);
 
        return true;
    }
 
    private static string GetCurrentBuildVersionString(Version baseVersion)
    {
        DateTime d = DateTime.Now;
        return new Version(baseVersion.Major, baseVersion.Minor,
            (DateTime.Today - new DateTime(2000, 1, 1)).Days,
            ((int)new TimeSpan(d.Hour, d.Minute, d.Second).TotalSeconds) / 2).ToString();
    }
}

Task class inherit from Microsoft.Build.Utilities.Task class from Microsoft.Build.Utilities.Core NuGet package. It takes BaseVersion property (optional) on input and returns generated version in Version output property. The logic to get version numbers is same as .NET automatic versioning (Build number is days count since 1/1/2000 and Revision is half seconds since midnight).

To build this MSBuild task, we use .NET Standard 1.3 class library project type with this class.

.csproj file can looks like this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard1.3</TargetFramework>
    <AssemblyName>DC.Build.Tasks</AssemblyName>
    <RootNamespace>DC.Build.Tasks</RootNamespace>
    <PackageId>DC.Build.Tasks</PackageId>
    <AssemblyTitle>DC.Build.Tasks</AssemblyTitle>
  </PropertyGroup>
 
  <ItemGroup>
    <PackageReference Include="Microsoft.Build.Framework" Version="15.1.1012" />
    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.1.1012" />
  </ItemGroup>
</Project>

This task project is also available in my GitHub holajan/DC.Build.Tasks

Now we setup MSBuild to use this task and set FileVersion and AssemblyVersion properties. In .csproj file, it looks like this:

<Project Sdk="Microsoft.NET.Sdk">
  <UsingTask TaskName="GetCurrentBuildVersion" AssemblyFile="$(MSBuildThisFileFullPath)\..\..\DC.Build.Tasks.dll" />
 
  <PropertyGroup>
    ...
    <AssemblyVersion>1.0.0.0</AssemblyVersion>
    <FileVersion>1.0.0.0</FileVersion>
  </PropertyGroup>
 
  ...
 
  <Target Name="BeforeBuildActionsProject1" BeforeTargets="BeforeBuild">
    <GetCurrentBuildVersion BaseVersion="$(FileVersion)">
      <Output TaskParameter="Version" PropertyName="FileVersion" />
    </GetCurrentBuildVersion>
    <PropertyGroup>
      <AssemblyVersion>$(FileVersion)</AssemblyVersion>
    </PropertyGroup>
  </Target>
 
</Project>

Important things here:

  • Mentioned UsingTask imports GetCurrentBuildVersion task from DC.Build.Tasks.dll. It assumes that this dll file is located on parent directory from your .csproj file.
  • Our BeforeBuildActionsProject1 Target that calls task must have unique name per project in case we have more projects in the solution which calls GetCurrentBuildVersion task.

The advantage of this solution is that it works not only from builds on build server, but also in manual builds from dotnet build or Visual Studio.