
ASP.NET Core applications are usually released often enough that versioning stops being a documentation concern and turns into delivery infrastructure. Once an application moves through CI, staging, production, containers, rollback procedures and incident response, the version number is no longer just a label. It becomes part of the operational model.
That changes the real question. It is not mainly whether a release should be called 6.4.0. The more important question is whether the identity of that release survives intact from Git commit to published output, deployment artifact and running process. If those layers disagree, a rollout may still succeed, but understanding the system later becomes needlessly expensive.
If the repository still lives on duplicated AssemblyInfo.cs values, wildcard auto increment or a public x.x.x.x release contract, start with How to migrate .NET versioning from x.x.x.x to SemVer
. This article assumes that baseline is already in place and focuses only on the web-specific part afterwards: published output, artifact promotion and runtime-visible build identity. For Windows executables, installers and MSIX-specific constraints, the companion article Modern versioning for .NET desktop apps and how to produce idempotent artifacts
covers the desktop side.
For ASP.NET Core, a useful versioning strategy therefore has to do two things at once:
- express release intent clearly
- produce artifacts that remain traceable as they move through environments
Nerdbank.GitVersioning is a strong fit because it anchors build identity in Git history while integrating directly into the .NET build.
Web delivery has more than one version surface
An ASP.NET Core application usually participates in several overlapping version conversations:
- product release version
- deployment artifact version
- API compatibility version
- database rollout compatibility
- static-asset cache identity
- container image tag
Those are related, but they are not interchangeable.
An API might ship product version 6.4.0 while still exposing v1 and v2 routes. Static assets may be cache-busted with hashes. A staged schema change may require temporary compatibility across two adjacent application versions. This is exactly why web versioning becomes brittle when the repository tries to express everything through one manually edited string.
Git should own version identity, not the pipeline
One of the most common failure patterns in web delivery is that the CI system quietly becomes the real source of version truth. A pipeline run number gets appended, a branch name is parsed, a tag may or may not be present and a build emits something that looks version-like.
That works right up to the point when the same commit has to be rebuilt, a release branch changes shape, or a hotfix comes from a different line. At that point the version turns out to describe pipeline circumstances more than source state.
Git-aware versioning is stronger because it derives identity from the repository itself. That means the same commit can be traced consistently even when builds run on different agents or at different times. In web systems, where debugging often depends on exact source-to-runtime traceability, that is a significant operational improvement.
The baseline release policy becomes operational during publish
The migration article covers how NBGV is introduced, why Git has to own version identity and what belongs in version.json. Once that baseline exists, the web-specific concern is how published artifacts opt into clean public release formatting without losing internal traceability.
In practice, publicReleaseRefSpec in version.json defines when public formatting should apply and many teams make that explicit during publish:
1dotnet publish -c Release -p:PublicRelease=true
That keeps commit-rich internal builds available where they help, while ensuring the formal deployment artifact is emitted in the representation that downstream tooling and operators expect.
Auto increment should follow repository history
Web systems still benefit from auto increment. The weak version of that idea is build-based incrementing, where every CI execution nudges a number forward. The stronger version is repository-based incrementing, where the version moves because source history moved.
That is the model NBGV provides. The declared release line stays stable in version.json, while Git history supplies version height and traceability. That makes the computed version meaningful in ways a pipeline counter never really is.
It also helps separate internal and public output. Internal builds often need commit-rich metadata. Public artifacts usually need a cleaner representation. NBGV supports that split through publicReleaseRefSpec and, when necessary, explicitly through the build property:
1dotnet publish -c Release -p:PublicRelease=true
Stable artifact identity starts before deployment
The most important rule in ASP.NET Core delivery is still simple: build once, promote many.
That rule only works when the artifact itself has a trustworthy identity. If the application is rebuilt for each environment, the version number may remain similar while the actual payload changes. At that point the release process has already lost its strongest guarantee.
This matters regardless of packaging format:
- publish directory zipped for deployment
- App Service package
- container image
- Kubernetes deployment artifact
The shape can change. The requirement does not.
Deterministic publish output depends on more than compilation
It is common to discuss determinism as if it only concerned the compiler. For web applications, dotnet publish depends on more than that:
- selected SDK
- restored dependency graph
- publish settings
- static-asset manifests
- packaging or containerization steps
That is why global.json belongs in the repository:
1{
2 "sdk": {
3 "version": "10.0.100"
4 }
5}
Without an SDK pin, rebuilding the same commit months later may produce different publish output simply because the agent moved to another feature band.
Runtime configuration must stay outside the artifact
Immutable artifacts only remain useful if the deployment model avoids environment-specific rebuilds. In ASP.NET Core that usually means configuration stays external and is injected at runtime instead of being baked into the compiled output.
That separation is easy to state and surprisingly easy to violate. The artifact needs to be the same in test, staging and production, with only configuration changing around it.
At the application level that means relying on the normal configuration stack rather than compile-time customization per environment:
1using Microsoft.AspNetCore.Builder;
2using Microsoft.Extensions.Configuration;
3using Microsoft.Extensions.DependencyInjection;
4using Microsoft.Extensions.Hosting;
5
6WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
7
8builder.Configuration.AddEnvironmentVariables();
9builder.Services.AddControllers();
10
11WebApplication app = builder.Build();
12
13if (app.Environment.IsDevelopment())
14{
15 app.UseDeveloperExceptionPage();
16}
17
18app.UseAuthorization();
19app.MapControllers();
20app.Run();
The principle is more important than the exact snippet: the runtime host supplies environment-specific behavior, not a fresh compilation.
Version reporting should be available from the running process
Deployment dashboards are useful, but they are not always enough during an incident. A running ASP.NET Core application should be able to report its own stamped identity.
NBGV’s generated ThisAssembly class makes that easy:
1WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
2WebApplication app = builder.Build();
3
4app.MapGet("/version", () =>
5{
6 VersionResponse response = new VersionResponse(
7 ThisAssembly.AssemblyInformationalVersion,
8 ThisAssembly.AssemblyFileVersion,
9 ThisAssembly.AssemblyVersion);
10
11 return Results.Ok(response);
12});
13
14app.Run();
15
16public sealed record VersionResponse(
17 string InformationalVersion,
18 string FileVersion,
19 string AssemblyVersion);
The endpoint itself is intentionally small. What matters is that the response comes from build-stamped metadata and not from a duplicated configuration value.
Monorepos benefit from pathFilters
Many ASP.NET Core repositories are not single-product repositories anymore. They may include APIs, workers, frontend assets, shared libraries and infrastructure code. In that layout, not every commit should advance every deployable unit.
pathFilters helps keep version height tied to relevant changes. A documentation-only commit or a frontend-only adjustment does not have to affect the identity of a backend API artifact. That keeps version numbers informative instead of merely busy.
The common failure modes are usually release-discipline problems
The most expensive versioning issues in web apps are rarely exotic MSBuild bugs. More often they come from weak release habits:
- rebuilding the same commit separately for each environment
- treating
latestas the only meaningful container tag - exposing no runtime-visible build identity
- letting pipeline run numbers define public versions
- pretending API version, product release and artifact identity are the same thing
NBGV does not solve those problems by magic, but it removes one of the main reasons they persist: a missing source of truth.
Conclusion
Good ASP.NET Core versioning is ultimately about preserving identity from source control to runtime. The commit, the build output, the deployment artifact and the running process should all tell the same story.
That is where NBGV earns its place. It gives the repository a practical workflow through nbgv, stamps build output directly and keeps release behavior in version.json instead of scattering it across pipelines and tribal memory. Combined with pinned SDKs and a build-once-promote-many deployment model, that produces web artifacts that are easier to trust under pressure.
Related articles

Jun 30, 2026 - 8 min read
Modern versioning for .NET desktop apps and how to produce idempotent artifacts
Desktop software makes versioning visible in a way that web systems often do not. A backend can hide a surprising amount of release …

Jun 26, 2026 - 9 min read
Modern versioning for .NET apps and libraries and how to produce idempotent artifacts
Versioning stays invisible right up to the point when it fails. A package has to be reissued, a support case depends on one exact binary, or …

Jun 24, 2026 - 14 min read
Local Aspire Development with Azure Cosmos DB and the Preview Emulator
Distributed applications tend to feel straightforward until a real cloud dependency enters the picture. Azure Cosmos DB is a good example. …
Let's Work Together
Looking for an experienced Platform Architect or Engineer for your next project? Whether it's cloud migration, platform modernization or building new solutions from scratch - I'm here to help you succeed.
