Versioning ASP.Net Core APIs with Swashbuckle - Making Space Potatoes V-x.x.x
Tags: csharp, webdev, dotnet
Published: 2019-08-26 21:50:01

[Updated Path Versioning Post Here](https://dev.to/htissink/asp-net-core-api-path-versioning-197o)

Creating a new API with ASP.NET Core is fun and easy; versioning that API is a bit harder. The cinch though is how generating Swagger for the initial version is a breeze while generating versioned swagger is a little trickier.

**Disclaimer: due to bugs in ASP.Net Core 2.2.0, please use ASP.Net Core 2.1.0**

Let's dive into this by creating a simple ASP.Net Core Api (2.1.0) to give us some _Space Potatoes_. First we'll create a controller called `SpaceFarmController`.

```cs
[Route("api/[controller]")]
[ApiController]
public class SpaceFarmController : ControllerBase
{
    [HttpGet("Potatoes")]
    public string SpacePotatoes() => "Space Potatoes v1";
}
```
Now, let's add some versioning to this _little_ API. Add the following Nuget Package to your project:

* **Microsoft.AspNetCore.Mvc.Versioning (version 3.0.0)**

Our API will be versioned using _Route Versioning_, this simply means that our API will be versioned according to its routes, with 

version|route
---|---
v1 | `.../api/controller/v1/something`
V2 | `.../api/controller/v2/something`
v{n} | `.../api/controller/v{n}/something`

The API can now easily be versioned by adding attributes to the controller:

```cs
[ApiVersion("1")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class SpaceFarmController : ControllerBase
{
	[HttpGet("Potatoes")]
	public string SpacePotatoes() => "Space Potatoes v1";
}
```

And modifying the `Startup.cs` file:

```cs
public void ConfigureServices(IServiceCollection services)
{
	services.AddMvc();
	services.AddApiVersioning(); // just add this
}
```

Running your app locally, you'll now be able to get some _Space Potatoes_ by making a GET request to `https://localhost:5001/api/v1/spacefarm/potatoes`.

Let's add some Swagger generation to the mix. For this we'll be using **Swashbuckle**. Add the following Nuget Package to your project:

* **Swashbuckle.AspNetCore (version 4.0.1)**

Adding Swagger generation to your project is now as simple as adding the following to your `Startup.cs` file:

```cs
public void ConfigureServices(IServiceCollection services)
{
	services.AddMvc();
	services.AddApiVersioning();

	// Add this
	services.AddSwaggerGen(options => 
		options.SwaggerDoc("v1", new Info
		{
			Version = "v1",
			Title = "v1 API",
			Description = "v1 API Description",
			TermsOfService = "Terms of Service v1"
		}));
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
	loggerFactory.AddConsole(Configuration.GetSection("Logging"));
	loggerFactory.AddDebug();

	// And add this, an endpoint for our swagger doc 
	app.UseSwagger();
	app.UseSwaggerUI(c =>
	{
		c.SwaggerEndpoint($"/swagger/v1/swagger.json", $"v1");
	});
	app.UseMvc();
}
```

For a final trick, change your `launchSettings.json` entry for your application to:

```cs
"*YOUR APPLICATION NAME HERE*": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "swagger",
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
```
Now when you run your application, it immediately launches the Swagger page.

If you've done all this correctly, when you run your app you should see a screen similar to:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/2tesbv8l6hxethh5f0iw.PNG)

This is great - you've got Swagger generation and a v1 of your API. But it's an ever changing world in the _Space Potato_ market, and pretty soon you're going to want to create an additional way to get _Space Potatoes_ - a v2.0 if you will.

Now, adding another version is quite easy, but getting the Swagger generation to work with this new version is a bit tricky.

First, let's add a **v2** for our endpoint in the `SpaceFarmController.cs`:

```cs
[Route("api/v{version:apiVersion}/[controller]")]
// Which versions the controller responds to
[ApiVersion("1")]
[ApiVersion("2")]
[ApiController]
public class SpaceFarmController : ControllerBase
{
	[HttpGet]
	// Which version the route corresponds to
	[MapToApiVersion("1")]
	[Route("Potatoes")]
	public string GetPotatoes() => "Space Potatoes v1";

	[HttpGet]
	// Which version the route corresponds to
	[MapToApiVersion("2")]
	[Route("Potatoes")]
	public string GetPotatoesV2() => "Space Potatoes v2";
}
```

Great, _fantastic_ even. We now have two versions of our API. If we run this, we can make requests to `https://localhost:5001/api/v1/spacefarm/potatoes` and `https://localhost:5001/api/v2/spacefarm/potatoes` (but the Swagger generation will fail).

Swashbuckle doesn't know that there is a difference in the two routes - it sees them as one and the same. So let's help Swashbuckle out. For this we'll need to create two classes:

```cs
public class RemoveVersionFromParameter : IOperationFilter
{
	public void Apply(Operation operation, OperationFilterContext context)
	{
		var versionParameter = operation.Parameters.Single(p => p.Name == "version");
		operation.Parameters.Remove(versionParameter);
	}
}
```

and

```cs
public class ReplaceVersionWithExactValueInPath : IDocumentFilter
{
	public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
	{
		swaggerDoc.Paths = swaggerDoc.Paths
			.ToDictionary(
				path => path.Key.Replace("v{version}", swaggerDoc.Info.Version),
				path => path.Value
			);
	}
}
```

Both classes a types of special _filters_ that Swashbuckle provides to aid Swagger generation. `RemoveVersionFromParameter` will remove the API Version as a parameter from the Swagger document. `ReplaceVersionWithExactValueInPath` will change the path from being variable `api/v{version:apiVersion}/[controller]` to having a fixed path e.g. `api/v1/[controller]` .

Finally, the filters will need to be applied to the Swagger generation in `Startup.cs`, and some other minor modifications need to be made:

```cs
public void ConfigureServices(IServiceCollection services)
{
	services.AddMvc();
	services.AddApiVersioning();

	services.AddApiVersioning(o =>
	{
		o.AssumeDefaultVersionWhenUnspecified = true;
		o.DefaultApiVersion = new ApiVersion(1, 0);
	});

	services.AddSwaggerGen(options =>
	{
		options.SwaggerDoc("v1",
			new Info
			{
				Version = "v1",
				Title = "v1 API",
				Description = "v1 API Description",
				TermsOfService = "Terms of usage v1"
			});

		// Add a SwaggerDoc for v2 
		options.SwaggerDoc("v2",
			new Info
			{
				Version = "v2",
				Title = "v2 API",
				Description = "v2 API Description",
				TermsOfService = "Terms of usage v3"
			});

                // Apply the filters
		options.OperationFilter<RemoveVersionFromParameter>();
		options.DocumentFilter<ReplaceVersionWithExactValueInPath>();

                // Ensure the routes are added to the right Swagger doc
		options.DocInclusionPredicate((version, desc) =>
		{
			var versions = desc.ControllerAttributes()
				.OfType<ApiVersionAttribute>()
				.SelectMany(attr => attr.Versions);

			var maps = desc.ActionAttributes()
				.OfType<MapToApiVersionAttribute>()
				.SelectMany(attr => attr.Versions)
				.ToArray();

			return versions.Any(v => $"v{v.ToString()}" == version)
						  && (!maps.Any() || maps.Any(v => $"v{v.ToString()}" == version));;
		});

	});

}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
	loggerFactory.AddConsole(Configuration.GetSection("Logging"));
	loggerFactory.AddDebug();

	app.UseSwagger();
	app.UseSwaggerUI(c =>
	{
		c.SwaggerEndpoint($"/swagger/v1/swagger.json", $"v1");
                // Specify and endpoint for v2
		c.SwaggerEndpoint($"/swagger/v2/swagger.json", $"v2");
	});
	app.UseMvc();
}
```

Most of what was done is quite self-explanatory. The only part that might be of interest is the `options.DocInclusionPredicate`. The little bit of code defining this predicate finds the versions attributed to the controller  and the version mappings associated with a particular endpoint, and ensures that the endpoint is included in the correct Swagger doc.

If everything has been done correctly, running the application should now launch a Swagger doc with multiple versions,

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/0e601fwh4gi4b5cspxzy.png)

And routes for each version:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/x4dj9x9atc0frem5k4vn.png)
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/cl48ginjm2hwfvdu3pp8.png)