Creating .NET Core API with versioning

ASP.NET Core logo bigger

In this post, I explain how creating .NET Core API with versioning in your ASP.NET projects. This post is helping me to add more functionalities in the AdminLTE project that I discuss in other posts (click here to see all posts). The source code of this post is on GitHub. In this blog I often talk about Swagger: here few links.

One of the major challenges surrounding exposing services is handling updates to the API contract. Clients may not want to update their applications when the API changes, so a versioning strategy becomes crucial. A versioning strategy allows clients to continue using the existing REST API and migrate their applications to the newer API when they are ready.

One way to version a REST API is to include the version number in the URI path.

This solution uses URI routing to point to a specific version of the API. Because cache keys (in this situation URIs) are changed by version, clients can easily cache resources. When a new version of the REST API is released, it is perceived as a new entry in the cache.

First let’s go creating .NET Core API with versioning.

.NET Core API with versioning

First, create new project “ASP.NET Core Web Application” then choose template “API”.

Run the project, it will open the browser and display json data, and see the url of the API, it doesn’t have version number

Now, let’s add versioning on the API

  • Open Package Manager, find Microsoft.AspNetCore.Mvc.Versioning and Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer then click install
  • On controller, above controller name, add below attributes
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
  • On Startup.cs class, add below codes on ConfigureServices method
services.AddApiVersioning(
    options =>
    {
        // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
        options.ReportApiVersions = true;
    });
services.AddVersionedApiExplorer(
    options =>
    {
        // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
        // note: the specified format code will format the version as "'v'major[.minor][-status]"
        options.GroupNameFormat = "'v'VVV";

        // note: this option is only necessary when versioning by url segment. the SubstitutionFormat
        // can also be used to control the format of the API version in route templates
        options.SubstituteApiVersionInUrl = true;
    });

Then run the project, it will display not found page, this is caused by we add versioning on our API, so we need to add version api/v1 before action name. So, we need to change the url. For example, before my api endpoint was by default https://localhost:44398/weatherforecast, now it is changed into https://localhost:44398/api/v1/weatherforecast

This is the result.

The API with versioning - API Version n. 1 - Creating .NET Core API with versioning
The API with versioning – API Version n. 1

Now let’s try adding same endpoint with different version, you could do this by adding new method on controller with attribute [ApiVersion(“{versionNumber”)]

Now our controller will have 2 methods.

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
    var rng = new Random();
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = rng.Next(-20, 55),
        Summary = Summaries[rng.Next(Summaries.Length)]
    })
    .ToArray();
}

[HttpGet]
[ApiVersion("2.0")]
public IEnumerable<WeatherForecast> GetV2()
{
    var rng = new Random();
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = rng.Next(1, 10),
        Summary = Summaries[rng.Next(Summaries.Length)]
    })
    .ToArray();
}

Run project, we will have different result on
https://localhost:44398/api/v1/weatherforecast and https://localhost:44398/api/v2/weatherforecast

The API with versioning - API Version n. 2 - Creating .NET Core API with versioning
The API with versioning – API Version n. 2

Swagger UI with versioning

  • First open package manager, find Swashbuckle.AspNetCore then click install
  • On root of project, add new class ConfigureSwaggerOptions
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;

namespace NETCoreAPIVersionAndSwaggerUI
{
    public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
    {
        readonly IApiVersionDescriptionProvider provider;

        public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) => this.provider = provider;

        /// <inheritdoc />
        public void Configure(SwaggerGenOptions options)
        {
            foreach (var description in provider.ApiVersionDescriptions)
            {
                try
                {
                    options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
                }
                catch (Exception)
                {

                }
            }
        }

        static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
        {
            var info = new OpenApiInfo()
            {
                Title = "Web Api Gateway",
                Version = description.ApiVersion.ToString()
            };

            if (description.IsDeprecated)
            {
                info.Description += " This API version has been deprecated.";
            }

            return info;
        }
    }
}

Then, on root of project add new class SwaggerDefaultValues and this is the implementation

using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace NETCoreAPIVersionAndSwaggerUI
{
    public class SwaggerDefaultValues : IOperationFilter
    {
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            var apiDescription = context.ApiDescription;

            operation.Deprecated |= apiDescription.IsDeprecated();

            if (operation.Parameters == null)
            {
                return;
            }

            foreach (var parameter in operation.Parameters)
            {
                var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);

                if (parameter.Description == null)
                {
                    parameter.Description = description.ModelMetadata?.Description;
                }


                parameter.Required |= description.IsRequired;
            }
        }
    }
}

Next, on Startup.cs, add below codes above constructor

static string XmlCommentsFileName
{
    get
    {
        var fileName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name + ".xml";
        return fileName;
    }
}

And then, at method ConfigureServices, add below codes

services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
services.AddSwaggerGen(
    options =>
    {
        // add a custom operation filter which sets default values
        options.OperationFilter<SwaggerDefaultValues>();

        // integrate xml comments
        options.IncludeXmlComments(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), 
            XmlCommentsFileName));

        options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
        {
            In = ParameterLocation.Header,
            Description = "Please insert JWT with Bearer into field",
            Name = "Authorization",
            Type = SecuritySchemeType.ApiKey
        });
        options.AddSecurityRequirement(new OpenApiSecurityRequirement {
            {
                new OpenApiSecurityScheme{
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Bearer"
                    }
                },
                new string[] { }
            }
        });
    });

Finally, in the same Startup.cs class change method Configure into

app.UseSwagger();
app.UseSwaggerUI(options =>
{
    // build a swagger endpoint for each discovered API version
    foreach (var description in provider.ApiVersionDescriptions)
    {
        options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", 
            description.GroupName.ToUpperInvariant());
    }
});

Now, I want to generate the XML documentation to use as description in the SwaggerUI. For that, right click on project on Solution explorer, choose Properties, navigate to tab Build, check XML documentation file then save.

Generate documentation for your project in Visual Studio - Creating .NET Core API with versioning
Generate documentation for your project in Visual Studio

Now, run the project. Then, navigate to https://localhost:44398/swagger and it will display SwaggerUI with versioning number.

SwaggerUI with API versioning - Creating .NET Core API with versioning
SwaggerUI with API versioning

Conclusion

In conclusion, I hope I gave you a good idea of how creating .NET Core API with versioning to add to your projects. The source code of this post is on GitHub.

One thought on “Creating .NET Core API with versioning

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.