API Versioning with Asp.Net Core Minimal APIs

I am an enthusiastic software developer focusing on C#, .NET, Asp.NET, Azure and Angular.
Search for a command to run...

I am an enthusiastic software developer focusing on C#, .NET, Asp.NET, Azure and Angular.
No comments yet. Be the first to comment.
gRPC, a high-performance, open-source RPC (Remote Procedure Call) framework developed by Google, has gained immense popularity among developers for building efficient and robust APIs. This blog post will explore how to get started with gRPC services ...

GraphQL is a powerful query language and runtime that efficiently fetches and manipulates data from APIs. This blog post will explore how to get started with GraphQL in Asp.Net Core. What is GraphQL? GraphQL is a query language and runtime for APIs t...

Observability is a key concept in modern software development. It refers to the ability of a system to be monitored, measured, and understood in order to identify and address issues. In the context of ASP.NET Core, observability can help developers a...

Introduction to Blazor Blazor is a web framework from Microsoft that allows developers to build interactive web applications using C# and HTML. With Blazor, we can create web applications that run entirely in the browser without the need for plugins ...

In this previous article, we learned about caching in Asp.Net Core. We talked about In-Memory cache and Distributed cache using SQL Server cache. This article will discuss how to use Redis for caching in Asp.Net Core. What is Redis? Redis (REmote DIc...

API versioning is the practice of managing changes to an API without breaking the client’s applications when new versions are developed. Versioning is an integral part of the API design. A good API versioning strategy communicates the changes made and allows clients using an existing REST API and only migrate or update their applications to the recent versions when they are ready.
There are many ways to version an API, each approach has its own set of strengths and weaknesses in addressing changes based on their scope. This article will cover the three following ways.
This is the most natural and straightforward approach. It includes the version number in the URL path and is often done with the prefix "v".
http://www.example.com/api/v1/todos
http://api.example.com/v1/todos
In this approach the version number is added as a query param making it easier to switch to the newest version in case a query parameter is not specified.
http://www.example.com/api/todos?version=1
This technique adds custom headers with version numbers as an attribute. This approach differs from query and URI versioning because it doesn’t add filler content to the URI.
Accept-version: v1
Accept-version: v2
To start the implementation first it is necessary to add the nugget package Asp.Versioning.Http. Once it is added we will be able to use the extension methods *AddApiVersioning * to define the strategy, WithApiVersionSet to define the version of the endpoint and HasApiVersion to define with versions are supported by the endpoint.
The following code is creating the version numbers that will be used in the version set system.
var version1 = new ApiVersion(1);
var version2 = new ApiVersion(2);
The version set tells the API versioning which versions are available to be used by the endpoints.
var versionSet = app.NewApiVersionSet()
.HasApiVersion(version1)
.HasApiVersion(version2)
.Build();
For this approach it is necessary to modify the route adding a new parameter to MapGet, MapPost or MapPut methods. It will be added a new segment to the URL called version that is limited to the type apiVersion. This is a validation created by the versioning system to ensure that correct values are passed.
app.MapGet("v{version:apiVersion}/todoitems", [Authorize] async (TodoDb db) =>
{
return await db.Todos.ToListAsync();
})
.WithApiVersionSet(versionSet)
.HasApiVersions(new[] { version1, version2 });
It is also necessary to configure the type of versioning that API will use. The following code shows how.
builder.Services.AddApiVersioning(opt =>
{
opt.ApiVersionReader = new UrlSegmentApiVersionReader();
});

For this approach It is not necessary to change the route of the endpoint, it only adds the versioning type that will be used.
builder.Services.AddApiVersioning(opt =>
{
opt.ApiVersionReader = new QueryStringApiVersionReader("version");
});

This approach also does not require changing the route and it offers two ways to accomplish the versioning, using an extension to the Accept header or using a custom header.
builder.Services.AddApiVersioning(opt =>
{
opt.ApiVersionReader = new MediaTypeApiVersionReader("version");
});

builder.Services.AddApiVersioning(opt =>
{
opt.ApiVersionReader = new HeaderApiVersionReader("Api-Version");
});

So now we can have two different versions of the same endpoint with different behaviors. In the following example the endpoint todoitems has version 1 with the old implementation and version 2 that changes the property CompletedTimestamp.
app.MapPost("v{version:apiVersion}/todoitems", [Authorize] async (IValidator<Todo> validator, Todo todo, TodoDb db) =>
{
ValidationResult validationResult = await validator.ValidateAsync(todo);
if (!validationResult.IsValid)
{
return Results.ValidationProblem(validationResult.ToDictionary());
}
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
})
.WithApiVersionSet(versionSet)
.HasApiVersion(version1);

app.MapPost("v{version:apiVersion}/todoitems", [Authorize] async (IValidator<Todo> validator, Todo todo, TodoDb db) =>
{
ValidationResult validationResult = await validator.ValidateAsync(todo);
if (!validationResult.IsValid)
{
return Results.ValidationProblem(validationResult.ToDictionary());
}
todo.CompletedTimestamp = DateTime.Now;
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
})
.WithName("newtodoitems")
.WithApiVersionSet(versionSet)
.HasApiVersion(version2);

The API will return an error when a version is required and was not specified or when the endpoint does not support the version used.


When the API version is not specified, it can be defined a default version that will be used.
builder.Services.AddApiVersioning(opt =>
{
opt.ApiVersionReader = new HeaderApiVersionReader("Api-Version");
opt.DefaultApiVersion = version1;
opt.AssumeDefaultVersionWhenUnspecified = true;
});
You can find the full code on my GitHub.