RichardSoeteman.NET

Automatic versioning for Umbraco packages

Published 12 January 2024

One thing that is always needed for Umbraco package development is adding a version number to your  package. Then you also need to make sure this version is not only added to your project files but also to your Umbraco package manifest. Doing this manually (like I did before) is not only boring but you also need to make sure you don't forget a file during find and replace.

GitVersion

The first thing I wanted to get rid of was calculating the next version  number manually. Lucky for me there is a tool called GitVersion. I am not going into much detail about the tool itself but basically what it does is calculate the next version number based on your GIT Repository. By the time you want to release a real version you specify a version tag and then GitVersion will use that version to calculate the next version. Really powerful. All options are in the docs but for a quick start I suggest reading this blogpost.

Apply version information automatically during build

Next step in the process is automatically apply the version number to the Assemblies (and NuGet packages) during build. Most of my consulting clients use Azure DevOps so I use that for my packages as well. In the Marketplace you can find the following 2 extensions. Probably those are available for GitHub Actions as well.

  • GitVersion to calculate the version number during build from the Pipeline.
  • Assembly Info to apply the new version number to .csproj files during build from the Pipeline.
Calculate new version number task

The task below in DevOps will calculate the version number based on the branch and store this in variables we can use later on during the build. A full overview of all variables can be found here.

- task: GitVersion@5
  displayName: 'Calculate new version number'
  inputs:
    versionSpec: '5.10.x'
    useConfigFile: true
    configFilePath: '$(Build.Repository.LocalPath)\Automation\GitVersion.yml'
Apply new version information to .csproject files

The next and last step during build is to get the version information from the GitVersion variables and apply that to the .csproj files so we get the version in our Assemblies and NuGet packages. 

- task: Assembly-Info-NetCore@3
  displayName: 'Apply new version information to .csproject files'
  inputs:
    Path: '$(Build.SourcesDirectory)'
    FileNames: '**/*.csproj'
    InsertAttributes: true
    FileEncoding: 'auto'
    WriteBOM: false
    VersionNumber: '$(GitVersion.MajorMinorPatch)'
    FileVersionNumber: '$(GitVersion.AssemblySemFileVer)'
    InformationalVersion: '$(GitVersion.NuGetVersion)'
    PackageVersion: '$(GitVersion.NuGetVersion)'
    LogLevel: 'verbose'
    FailOnWarning: false
    DisableTelemetry: false
Use the version information in Umbraco

At this stage we automated the build but we still need to manually update our package manifest and update version numbers in code where we want to display the version to our users. To solve this I created a simple VersionHelper that is registered in a composer to get the version number from the assembly.

  public class VersionInfoHelper : IVersionInfoHelper
  {
      string? _version = null;

      /// <summary>
      /// Gets the version of the package .
      /// </summary>
      /// <returns></returns>
      public string GetVersion()
      {
          _version ??= GetVersionFromAssembly();
          return _version;
      }

      /// <summary>
      /// Gets the version from assembly, called lazy loaded so only called once.
      /// </summary>
      private string GetVersionFromAssembly()
      {
          return StripInformationalBuildSHAFromVersion(GetType().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? string.Empty);
      }

      /// <summary>
      /// Strips the informational build SHA from version.
      ///  1.0.0-beta1+8e29087e722d33bc5894dbcf875c27b1c4aab4cb wil be returned as 1.0.0-beta1
      /// </summary>
      /// <param name="version">The version.</param>
      /// <returns></returns>
      private static string StripInformationalBuildSHAFromVersion(string version)
      {
          return version.Split('+')[0];
      }
  }
Package manifest

I usually register my package using a ManifestFilter instead of a manifest file in my app_plugins folder. Benefit of this is that I can use external dependencies in the filter.

In the filter below you can see that I use the VersionHelper to set the version number on the manifest. And of course you can do the same if you need the version number elsewhere in the package (like I did on a dashboard).

public class ManifestFilter : IManifestFilter
{
    private readonly IVersionInfoHelper _versionInfoHelper;

    public ManifestFilter(IVersionInfoHelper versionInfoHelper)
    {
        _versionInfoHelper = versionInfoHelper;
    }

    public void Filter(List<PackageManifest> manifests)
    {
        manifests.Add(new PackageManifest
        {
            PackageName = "Devops Talk",
            Version = _versionInfoHelper.GetVersion(),
            AllowPackageTelemetry = false,
            BundleOptions = BundleOptions.Independent,
            Dashboards = new[] { new ManifestDashboard
                {
                    Alias = "devopsTalkDashboard",
                    View = "/App_Plugins/DevOpsTalk/memberresetdashboard.html",
                    Sections = new[] {"member" }
                }
            },
            Scripts = new[]
                {
                "/App_Plugins/DevOpsTalk/memberresetdashboard.controller.js"
                }
        });
    }
}
End result same version all over the place

Below the end result, one version number all over the place without having to think about it. 

image