Giving dwCheckApi That Swagger

Jamie Taylor
Giving dwCheckApi That Swagger Header ImageSource: https://unsplash.com/photos/jZ2ro_xbKZ8 Copyright 2017: Daniel von Appen (@daniel_von_appen)

Today’s header image was created by Daniel von Appen at Unsplash

Imagine the situation: you’ve found an amazing API, but you’ve no idea how to use it. What’s a developer to do?

In the case of dwCheckApi, early on in the design phase I decided to have anything which wasn’t an API endpoint return string which described how to use the API. That way anyone who found it would know exactly how to use it.

The current version of the API help screen for dwCheckApi

The only issue with that screen is that it isn’t interactive or particularly useful to the consumers of my API.

the content-type is ‘text/plain’

So I decided to add a little swagger to my API.

Got That API Like Swagger

What’s Swagger?

glad you asked, other wise this post would be quite short

Swagger is a way to automatically create API documentation which conforms to the Open API Standard. Did I mention that Swagger is part of the OpenAPI Initiative, too? I didn’t? Well they are. And they are in great company, too:

  • Linux Foundation
  • Google
  • Microsoft
  • PayPal
  • IBM

and a bunch of others.

The Swagger API documentation can be served one of two ways (or both)

as we’ll see later

either as a JSON object which can be consumed by some other application, or as a fancy-shmancy web ui.

dwCheckApi Swagger UI
I told you it was fancy

That’s what dwCheckApi’s Swagger UI looks like, and it’s pretty swish.

if you follow this link, you can see it for yourself.

It isn’t just this humble developer

humble? ha!

who thinks that having Swagger is a good idea, either. Here’s a quote from the opening paragraph from a recent article on Build Azure:

In short, Swagger is “the world’s most popular API tooling”. It’s a powerful open source framework backed by a very large ecosystem of tools that help you design, build, document, and consume your RESTful APIs. The Swagger v3.0 Specification was contributed to the OpenAPI Initiative, and thus Swagger has been merged with OpenAPI. Basically, Swagger is now OpenAPI.

source: https://buildazure.com/2017/10/20/swagger-is-now-the-openapi-specification/

But how did I get it to do that?

Getting That API Like Swagger

I’ll drop the Maroon 5 references in a minute

or maybe I wont

Now that you’ve seen how ace Swagger is, let’s talk about how you can go about setting up Swagger for your API.

The first thing you’ll need to do is add a NuGet package called Swashbuckle.AspNetCore to your API project. You can do this in the terminal like this:

dotnet add package Swashbuckle.AspNetCore

or you can manually edit your csproj so that it has the following line:

<PackageReference Include="Swashbuckle.AspNetCore" Version="1.1.0" />

the version number presented here is the latest at the time of writing this post

You’ll then need to restore packages (if you’re using an IDE, this will be done for you):

dotnet restore

Then you’ll need to make a few changes in your startup class.

The Startup Class

You need to add Swagger to both the container (in the Configure Services method) and the HTTP Pipeline (in the Configure method). I always like to start with the container, but you can do it either way.

unless you want to follow this post to the letter, that is

Adding Swagger to the Container

I like to keep my startup class tidy and easy to read by using extension methods

I’ve written about how and why before

and this will be no exception. You don’t have to use extension methods in your startup, but this example is going to have them.

My extension method for adding Swagger to the container looks like this:

public static void AddSwagger(this IServiceCollection serviceCollection, string versionNumberString,
bool includeXmlDocumentation = true)
{
// Register the Swagger generator, defining one or more Swagger documents
serviceCollection.AddSwaggerGen(c =>
{
c.SwaggerDoc($"v{versionNumberString}",
new Info
{
Title = "dwCheckApi",
Version = $"v{versionNumberString}",
Description = "A simple APi to get the details on Books, Characters and Series within a canon of novels",
Contact = new Contact
{
Name = "Jamie Taylor",
Email = "",
Url = "https://dotnetcore.gaprogman.com"
}
}
);
if (!includeXmlDocumentation) return;
// Set the comments path for the Swagger JSON and UI.
var basePath = PlatformServices.Default.Application.ApplicationBasePath;
var xmlPath = Path.Combine(basePath, "dwCheckApi.xml");
if (File.Exists(xmlPath))
{
c.IncludeXmlComments(xmlPath);
}
});
}

As you can see, this method is a wrapper around a call to `AddSwaggerGen`, which takes in a tonne of arguments

well, not really a tonne. But a lot

Let’s take a look at the first chunk of code and see what’s going on:

c.SwaggerDoc($"v{versionNumberString}",

The first thing we’re doing (on line 80) is using the version number for the application

we’ll see how we can pass that in, in a moment

and providing that as the version number identifier string for Swagger’s SwaggerDoc class.

new Info
{
Title = "dwCheckApi",
Version = $"v{versionNumberString}",
Description = "A simple APi to get the details on Books, Characters and Series within a canon of novels",
Contact = new Contact
{
Name = "Jamie Taylor",
Email = "",
Url = "https://dotnetcore.gaprogman.com"
}
}

Next we’re setting up an Info object. This contains a bunch of useful information about the API – this information is shown at the head of the Swagger page when you browse to it.

if (!includeXmlDocumentation) return;
// Set the comments path for the Swagger JSON and UI.
var basePath = PlatformServices.Default.Application.ApplicationBasePath;
var xmlPath = Path.Combine(basePath, "dwCheckApi.xml");
if (File.Exists(xmlPath))
{
c.IncludeXmlComments(xmlPath);
}
});

Finally, we’re conditionally adding any XML Documentation (created at build time) for the application. These provide more information for each of the API controller actions which appear in the Swagger UI.

Back To The Startup Class

Now we need to consume this new extension method, and we do it like so:

public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
{
"text/plain", "application/json"
});
});
services.AddCustomizedMvc();
services.AddCorsPolicy();
services.AddDbContext();
services.AddTransientServices();
services.AddSwagger(CommonHelpers.GetVersionNumber());
}

We’re using a helper method (GetVersionNumber) to get the version number of the application, so let’s take a look at how that works.

GetVersionNumber

This method simply returns the version number of the application, like so:

using System;
using System.Reflection;
namespace dwCheckApi.Helpers
{
public static class CommonHelpers
{
public static string GetVersionNumber()
{
return Assembly.GetEntryAssembly()
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
.InformationalVersion;
}
}
}
Caveat Number 1

For the above to work, we need to have a correctly formatted Version tag in your APIs csproj, like so:

<VersionPrefix>2.2.0</VersionPrefix>
Caveat Number 2

If you use something like the above version tag and have a VersionnSuffix tag, then Swagger will not work.

In other words, if you have something like this in your csproj:

<VersionPrefix>2.2.0</VersionPrefix>
<VersionSuffix>Alpha</VersionSuffix>

Then Swagger will fail to render the API JSON, which is used by the Swagger UI.

Adding Swagger to the HTTP Pipeline

As with adding stuff to the Container, I like to use extension methods to keep my HTTP Pipeline set up tidy. Here’s the extension method I created for telling the HTTP pipeline to use Swagger:

public static void UseSwagger(this IApplicationBuilder applicationBuilder,
string swaggerUrl, string swaggerDescription)
{
// Enable middleware to serve generated Swagger as a JSON endpoint.
applicationBuilder.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying
// the Swagger JSON endpoint.
applicationBuilder.UseSwaggerUI(c =>
{
c.SwaggerEndpoint(swaggerUrl, swaggerDescription);
});
}

This is a lot simpler to read and understand than the method for adding Swagger to the container. If we just want the JSON for our API, we can include the code at line 59:

applicationBuilder.UseSwagger();

If we want the Swagger UI too, then we need to include the code between lines 63 and 66:

applicationBuilder.UseSwaggerUI(c =>
{
c.SwaggerEndpoint(swaggerUrl, swaggerDescription);
});
Back To The Startup Class

Now we need to consume this new extension method, and we do it like so:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.EnsureDatabaseIsSeeded(false);
}
app.UseResponseCompression();
app.GnuTerryPratchett();
app.UseCorsPolicy();
app.UseStaticFiles();
app.UseSwagger($"/swagger/v{CommonHelpers.GetVersionNumber()}/swagger.json",
$"dwCheckApi {CommonHelpers.GetVersionNumber()}");
app.UseCustomisedMvc();
}

Again, we’re using the GetVersionNumber helper method here. The first argument is the endpoint that Swagger will serve the JSON API data from, and the second is a little description of the API we’re using Swagger for.

Lemme See That Swagger

Now that you’ve added the above code, run the application (either in your IDE or from the terminal with dotnet run) and head to the URL it gives you.

depending on how you’ve got your application set up, this might be localhost:5000

Once you’re there, and you’ve seen that your application is running, change the URL so that you’re accessing /swagger and you should get something similar to the following:

dev swagger ui basic
The methods show here are specific to dwCheckApi, but you should have something very similar

XML Documentation FTW

We can make the information presented in the Swagger UI about our API a lot more specific by adding XML documentation.

if you’re not sure, I’d take a look at that link

The first thing we need to do is add some stuff to the csproj to tell the .NET Core build tooling to generate XML documentation files for our application at build time. This is really easy to do, just add the following lines:

<!-- Add XML documentation for Swagger (debug) -->
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>bin\Debug\netcoreapp2.0\dwCheckApi.xml</DocumentationFile>
</PropertyGroup>
<!-- Add XML documentation for Swagger (release) -->
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile>bin\Release\netcoreapp2.0\dwCheckApi.xml</DocumentationFile>
</PropertyGroup>

These lines tell the .NET Core build tooling to generate the XML documentation for both debug and release builds and place the generated files in the bin directory, along side the built application.

Next we need to visit each of the API methods in each of the Controllers in the application and add XML documentation to them.

Here’s what one of the controller methods in my BooksController looks like, once I’ve added the XML documentation to it:

/// <summary>
/// Used to get a Book record by its ordinal (the order in which it was released)
/// </summary>
/// <param name="id">The ordinal of a Book to return</param>
/// <returns>
/// If a Book record can be found, then a <see cref="BaseController.SingleResult"/>
/// is returned, which contains a <see cref="dwCheckApi.DTO.ViewModels.BookViewModel"/>.
/// If no record can be found, then an <see cref="BaseController.ErrorResponse"/> is returned
/// </returns>
[HttpGet("Get/{id}")]
public JsonResult GetByOrdinal(int id)
{
var book = _bookService.FindByOrdinal(id);
if (book == null)
{
return ErrorResponse("Not found");
}
return SingleResult(BookViewModelHelpers.ConvertToViewModel(book));
}

I’ve highlighted the XML documentation, for no real reason

Now when you re-run the application and head back over to /swagger

because we’ve changed code which is generated at build time, you’ll need to re-build

you should get more detailed API documentation. Here’s what dwCheckApi looks like once XML Documentation has been added to it:

dev swagger ui xml documentation
I’ve expanded the same controller action method from the previous screen shot

That’s Not All

There’s a lot that Swagger can do. It can show examples of requests and responses, for instance. And it will highlight the different HTTP verbs

  • GET
  • POST
  • DELETE
  • etc

in different colours. It’s worth taking a look at the Swagger documentation to see all of the other cool things that it can do to help you describe your API to your consumers.

One Last Thing

what are you, Columbo?

Depending on the requirements for your application, you may want to only include Swagger in the Development environment. If that’s the case, all you need to do is change where you call the UseSwagger extension method in the Configure method, like so:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.EnsureDatabaseIsSeeded(false);
app.UseSwagger($"/swagger/v{CommonHelpers.GetVersionNumber()}/swagger.json",
$"dwCheckApi {CommonHelpers.GetVersionNumber()}");
}
app.UseResponseCompression();
app.GnuTerryPratchett();
app.UseCorsPolicy();
app.UseStaticFiles();
app.UseCustomisedMvc();
}

And there you have it. You now know how to enable Swagger on your APIs.

go forth and get your swagger on

Have you used Swagger before, perhaps on a .NET Framework (or any of the other .NET Standard compatible platforms)? Was it as easy to set up as this?

If you’ve not set up Swagger on one of your APIs, why not? Is there something specific which is stopping you?

Let me know in the comments and let’s keep the conversation going.

If this post has helped you out, please consider   Buy me a coffeeBuying me a coffee
Jamie Taylor
A .NET developer specialising in ASP.NET MVC websites and services, with a background in WinForms and Games Development. When not programming using .NET, he is either learning about .NET Core (and usually building something cross platform with it), speaking Japanese to anyone who'll listen, learning about languages, writing for his non-dev blog, or writing for a blog about video games (which he runs with his brother)