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 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...
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.