Jeremy Davis
Jeremy Davis
Sitecore, C# and web development
Article printed from: https://blog.jermdavis.dev/posts/2023/build-metadata-dotnet

Adding build metadata to your .Net code

Ever wanted build-time data available at runtime?

Published 10 April 2023
.Net C# ~1 min. read

The other day I had a scenario where I wanted to be able to display the date an app was built in its UI. While you can always fall back to a "just make that a string in your code" approach, after a bit of digging I discovered a better way. It turns out recent .Net code has some clever patterns to help with this...

The basics are simple

The obvious way to tag code in C# with metadata like "when were you compiled" is to use an Attribute. It's pretty trivial to whip up a custom attribute for marking an app with what you need:

[AttributeUsage(AttributeTargets.Assembly)]
internal class BuiltAtAttribute : Attribute
{
    public BuildDateAttribute(string value)
    {
        DateTime = DateTime.ParseExact(value, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None);
    }

    public DateTime DateTime { get; }
}

					

And with that in your code, you can open up your AssemblyInfo.cs file and add a line for

[assembly: BuiltAt("20230330140538")]

					

And then add some code to your app to read this data back out at runtime:

var assembly = typeof(YourApp).Assembly;
var attribute = assembly.GetCustomAttribute<BuiltAtAttribute>();
var builtAt = attribute.DateTime;

					

Simple...

Can we avoid manual steps?

But this means you have to manually set the right string in your code for every build - and you have to not mess up the format. That is less than ideal. I for one am very good at forgetting, and messing up DateTime format patterns...

But it turns out that MSBuild has some magic we can make use of here. There's a less well known MSBuild file element (Well, less known to me at least) called AssemblyAttribute which allows you to dynamically create attribute values when the build runs. So instead of manually putting this data into the AssemblyInfo.cs file, the compiler can sort this out for us.

The syntax is pretty easy. In an ItemGroup in your .csproj file, you add an AssemblyAttribute element, and give it the name of the attribute you want added, as well as the data you want to pass as its constructor parameters:

<ItemGroup>
	<AssemblyAttribute Include="BuiltAtAttribute">
		<_Parameter1>$([System.DateTime]::UtcNow.ToString("yyyyMMddHHmmss"))</_Parameter1>
	</AssemblyAttribute>
</ItemGroup>

					

Each time the build runs, the expression in _Parameter1 here will be evaluated, and the data will be written into the constructor for an instance of BuiltAtAttribute. And the results of this get written to one of the dynamically generated .cs files which end up compiled into your app.

If you look in the obj folder for your build type (Release or Debug) and framework version then you'll find a <yourProject>.AssemblyInfo.cs file which includes this dynamically generated code along with some other stuff that MSBuild has wired up for us:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Reflection;

[assembly: BuildDateAttribute("20230330140538")]
[assembly: System.Reflection.AssemblyCompanyAttribute("MyApp")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("MyApp")]
[assembly: System.Reflection.AssemblyTitleAttribute("MyApp")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
[assembly: System.Runtime.Versioning.TargetPlatformAttribute("Windows7.0")]
[assembly: System.Runtime.Versioning.SupportedOSPlatformAttribute("Windows7.0")]

// Generated by the MSBuild WriteCodeFragment class.

					

So that metadata will always be up to date now, and even I can't mess it up...

This behaviour is based on MSBuild's WriteCodeFragment Task - which appears to be supported from VS2015 onwards - though I'm less sure which version the use of AssemblyAttribute started with.

↑ Back to top