The title of this post is hugely specific, but the idea is simple. We want to answer the question: "what codebase is running in Production right now?" Many is the time where I've been pondering over why something isn't working as expected and burned a disappointing amount of time before realising that I'm playing with an old version of an app. Wouldn't it be great give our app a way to say: "Hey! I'm version 18.104.22.168 of your app; built from this commit hash, I was built on Wednesday, I was the nineth build that day and I was built from the
main branch. And I'm an Aries." Or something like that.
This post was inspired by Scott Hanselman's similar post on the topic. Ultimately this ended up going in a fairly different direction and so seemed worthy of a post of its own.
A particular difference is that this is targeting SPAs. Famously, cache invalidation is hard. It's possible for the HTML/JS/CSS of your app to be stale due to aggressive caching. So we're going to make it possible to see build information for both when the SPA (or "client") is built, as well as when the .NET app (or "server") is built. We're using a specific type of SPA here; a React SPA built with TypeScript and Material UI, however the principles here are general; you could surface this up any which way you choose.
Putting build info into
The first thing we're going to do is to inject our build details into two identical
buildinfo.json files; one that sits in the server codebase and which will be used to drive the server build information, and one that sits in the client codebase to drive the client equivalent. They'll end up looking something like this:
We generate this by adding the following
yml to the beginning of our
azure-pipelines.yml (crucially before the client or server build take place):
As you can see, we're placing the following variables that are available at build time in Azure Pipelines, into the
BuildNumber- The name of the completed build; which usually takes the form of a date in the
yyyyMMddformat, suffixed by
xis a number that increments representing the number of builds that have taken place on the given day.
BuildId- The ID of the record for the completed build.
SourceVersion- This is the commit hash of the source code in Git
SourceBranchName- The name of the branch in Git.
There's many variables available in Azure Pipelines that can be used - we've picked out the ones most interesting to us.
Our pipeline is dropping the
buildinfo.json over pre-existing stub
buildinfo.json files in both our client and server codebases. The stub files look like this:
In our .NET app, the
buildinfo.json file has been dropped in the root of the app. And as luck would have it, all JSON files are automatically included in a .NET build and so it will be available at runtime. We want to surface this file through an API, and we also want to use it to stamp details into our logs.
So we need to parse the file, and for that we'll use this:
The above code reads the
buildinfo.json file and deserialises it into a
BuildInfo record which is then surfaced up by the
GetBuildInfo method. We initialise this at the start of our
Program.cs like so:
Now we need a controller to surface this information up. We'll add ourselves a
This exposes an
api/build endpoint in our .NET app that, when hit, will display the following JSON:
Our server now lets the world know which version it is running and this is tremendous. Now let's make our client do the same.
Very little is required to achieve this. Again we have a
buildinfo.json sat in the root of our codebase. We're able to import it as a module in TypeScript because we've set the following property in our
As a consequence, consumption is as simple as:
Which provides us with a
clientBuildInfo which TypeScript automatically derives as this type:
How you choose to use that information is entirely your choice. We're going to add ourselves an "about" screen in our app, which displays both client info (loaded using the mechanism above) and server info (
fetched from the
When the above page is viewed it looks like this:
And that's it! Our app is clearly telling us what version is being run, both on the server and in the client. Thanks to Scott Hanselman for his work which inspired this.