Documenting your ASP.NET Core Web API with Swashbuckle
So you’ve finished your API, it’s awesome. Does everything you expect it to do, you’ve got great unit and integration tests to cover a percentage that makes you happy and handles exceptions and resource management flawlessly. Let’s go a step further and say that your API solves a really useful problem that would necessitate someone asking if they can consume it. You say “No problem! To get started you just need to read the … … “. At that point you realize that you forgot the documentation. Up until now (and I’ve been there) you haven’t even thought of the documentation because you have been so focused on finishing features. What’s a developer to do?
I can’t speak for every situation but in the case of creating beautiful, Swagger standard docs when developing with ASP.NET Core there is an awesome library called Swashbuckle that can help you out. Feel free to take a read over at Github which covers the project and the purpose but what I’ll walk you through below what you need to do and what you’ll get from an output standpoint by following along. One aside before we begin, this won’t cover hosting your API docs but once you’ve got the JSON that this creates, any Swagger container will do. Swaggerhub, SwaggerUI or even a WordPress plugin
Let’s Get Started
I’m a big golfer so today’s API is going to be called “Golf Bag API”. The operations that I’m planning on support our based CRUD for Golf Balls and Clubs as they relate to your golf bag. As with most examples, it is just enough to get you started but it is runnable and you’ll get a good feel for how things work.
When we are done, you’ll have a local endpoint the produces this
Project Setup
Fire up a new ASP.NET Web API project. I did so using Rider but you could use Visual Studio or the CLI. Now run dotnet add package Swashbuckle.AspNetCore to add Swashbuckle to your project. As you can see, I’m supporting the operations with 2 controllers and a pair of DTOs to match.
I generally like to keep my controllers small and more coordinators and often have probably more than I should, but I like granularity and clear explanation. Additionally, I never like to send my Domain Objects across the wire and often try to only send what is need.
Controller Work
Diving into into the ClubsController and let’s take a look at the [HttpGet] . You have a few options in terms of generating your output
- ProducesReponseType is used to determine the value status codes that the action will return. Providing a Type will include the DTO or the payload that actually is returned when this status code is determined. So if you if you have Error objects for specific conditions you can specify those as well.
- XML comments on the action will generate comments on the output to be used in the documentation.
Both of these are optional, but the more you can provide for essentially free I feel the better
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
/// <summary> /// Returns a list of clubs in your bag /// </summary> /// <returns></returns> [HttpGet] [ProducesResponseType(200, Type = typeof(List<ClubDto>))] [ProducesResponseType(401)] [ProducesResponseType(404)] [ProducesResponseType(500)] public ActionResult<IEnumerable<string>> Get() { return new OkObjectResult( new [] { new ClubDto {Name = "Sand wedge", Description = "Sand wedge", ClubType = ClubType.Iron}, new ClubDto {Name = "Pitching wedge", Description = "Pitching wedge", ClubType = ClubType.Iron}, new ClubDto {Name = "9-iron", Description = "9-iron", ClubType = ClubType.Iron}, new ClubDto {Name = "8-iron", Description = "8-iron", ClubType = ClubType.Iron}, new ClubDto {Name = "7-iron", Description = "7-iron", ClubType = ClubType.Iron}, new ClubDto {Name = "6-iron", Description = "6-iron", ClubType = ClubType.Iron}, new ClubDto {Name = "5-iron", Description = "5-iron", ClubType = ClubType.Iron}, new ClubDto {Name = "4-iron", Description = "4-iron", ClubType = ClubType.Iron}, new ClubDto {Name = "3-iron", Description = "3-iron", ClubType = ClubType.Iron}, new ClubDto {Name = "2-iron", Description = "2-iron", ClubType = ClubType.Iron}, new ClubDto {Name = "3-wood", Description = "3-wood", ClubType = ClubType.Wood}, new ClubDto {Name = "Driver", Description = "Driver", ClubType = ClubType.Wood}, new ClubDto {Name = "Putter", Description = "Putter", ClubType = ClubType.Putter} }); } |
This action produces something that looks just like this.
Take a look at that example payload. Isn’t that nice?!? Notice the “Model” text next example value. Here’s the trick, if you attribute your action correctly you get that support. And that links down to the actual request/response which Swagger defines down at the bottom of your Docs. Take it a step further. If you attribute and comment your models with XML comments, DataContract and the other DataAnnotations attributes then your docs get even that much richer. Here’s the ClubDto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/// <summary> /// Represents a golf club /// </summary> [DataContract] public class ClubDto { /// <summary> /// The name of the club (1-iron, 2-iron) etc /// </summary> [DataMember] [Required] public string Name { get; set; } /// <summary> /// Description like "this is my favorite club" /// </summary> [DataMember] public string Description { get; set; } /// <summary> /// Iron, wood, putter /// </summary> [DataMember] [Required] public ClubType ClubType { get; set; } } |
And here is the representation of that in the documentation
Pretty cool isn’t it? So thinking through this as a developer against your API, they now know
- What the URI looks like
- What verbs you accept
- What querystring, route and post parameters you take
- What status codes the endpoint will return in addition to the payload
- And definitions about the payloads themselves with strongly typed representations of what those underlying values are and the usages of those properties
And all of that by just attributing your controller actions and requests/responses.
Wiring It Up
So how does Swashbuckle know to do this? We next need to make some modifications to our Startup.cs file.
Step 1
Inside of our ConfigureServices I’ve made these changes
1 2 3 4 5 6 7 8 9 10 |
services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new Info {Title = "Golf Bag API", Version = "v1"}); c.DocInclusionPredicate((_, api) => !string.IsNullOrWhiteSpace(api.GroupName)); c.TagActionsBy(api => api.GroupName); var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); }); |
What does that do?
- Provides configuration options
- Specifics the title and version for the swagger doc
- Tells Swashbuckle to group by GroupName (there are lots of options and the ability to override and customize this)
- Build in XML comments that are on the top of your props, classes and methods
Quick side note, building XML comments. Rider in my case doesn’t have a checkbox to build this like Visual Studio does. However, a quick edit of the csproj file will suffice. The nowarn tells the compiler not to generate warnings based upon missing or incomplete comments.
1 2 3 4 |
<PropertyGroup> <GenerateDocumentationFile>true</GenerateDocumentationFile> <NoWarn>$(NoWarn);1591</NoWarn> </PropertyGroup> |
Step 2
The second part needs a modification to the Configure method
1 2 3 4 5 6 7 8 9 |
app.UseSwagger() .UseSwaggerUI( c => { c.RoutePrefix = "swagger"; c.SwaggerEndpoint("/swagger/v1/swagger.json", "Golf bag API Docs"); c.ShowExtensions(); } ); |
We need to tell the ApplicationBuilder to use Swagger and then include SwaggerUI as something your API can now host (lots of ways to host Swagger but this is a nice convenience)
- Add the route prefix “swagger”
- Specify where the JSON file will live and the name
Now once you build and run you can hit http://localhost:5000/swagger and you get something that looks just like the initial picture in this post.
Wrapping Up
I’m hopeful that you able to follow along and can see how easy this was to get high quality documentation for your APIs with minimal effort. I know on my projects currently, I just include this level of attribution and XML commenting as part of my dev flow because I know what I’m going to get on the backend. And for those developers that will be working off your API down the line, they will be grateful.
If you are interested in the code it is located here. https://github.com/benbpyle/swashbuckle.example
I’m going to continue to try and deliver new content each week so please check back and I do appreciate you taking the time to read.






