I recently worked with Swashbuckle, a .NET package that can generate Swagger 2.0 and OpenAPI 3.0 specifications from existing ASP.NET Core solutions. The benefit of the library is we can write HTTP APIs and get automatically generated documentation.
One issue that has bothered me about Swashbuckle, at least historically, is the generation of documentation that happens at runtime, using precious CPU and Memory. To generate a specification, Swashbuckle must work through routes, controllers, models, and comments. All this work requires heavy reflection. The final artifact is either a YAML or JSON file describing our API. In most cases, our specifications are static and unchanging until the subsequent deployment of our API.
This post will see how to configure Swashbuckle within an ASP.NET Core project to generate a static OpenAPI specification file using the CLI tooling.
Swashbuckle Default Setup
Starting with a brand new ASP.NET Core Web API application, we’ll see Swashbuckle is set up by default. So we first see SwaggerGen
is in the ConfigureServices
method.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo {Title = "WebApplication10", Version = "v1"});
});
}
We also see that the SwaggerMiddleware
and the Swagger UI are registered in the Configure
method.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication10 v1"));
}
// other code...
}
You’ll need to install Swashbuckle and go through the steps to configure your project if you do not see these lines of code in your Start-up
class. Running the web project will let us access our Swagger UI at https://localhost/swagger/ui/index.html
.
Let’s look at how we can continue to use Swashbuckle to generate our OpenAPI specification without using the middleware to generate our specification at runtime.
Generating OpenAPI With .NET CLI
The first step is to make sure our project can call the Swashbuckle CLI tooling from the context of our solution. To do this, we’ll first run the .NET CLI command to create a local tool manifest.
dotnet new tool-manifest
We’ll then follow it up with the installation of the Swashbuckle CLI tooling.
dotnet tool install SwashBuckle.AspNetCore.Cli
We should now be able to run the tooling as a build step. We’ll do that by altering our web project’s csproj
. Be sure to create the wwwroot\swagger\v1
directory first.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\swagger\v1" />
</ItemGroup>
<Target Name="OpenAPI" AfterTargets="Build" Condition="$(Configuration)=='Debug'">
<Exec Command="dotnet swagger tofile --output ./wwwroot/swagger/v1/swagger.yaml --yaml $(OutputPath)$(AssemblyName).dll v1" WorkingDirectory="$(ProjectDir)" />
<Exec Command="dotnet swagger tofile --output ./wwwroot/swagger/v1/swagger.json $(OutputPath)$(AssemblyName).dll v1" WorkingDirectory="$(ProjectDir)" />
</Target>
</Project>
Now, when we build our project, we’ll see the files swagger.json
and swagger.yaml
as static files.
But we’re not done yet.
Serving Static OpenAPI Specifications
To take advantage our files within our web application, we’ll need to modify our Start-up
file to serve these static files. First, let’s add our servers to our OpenAPI specification files. It makes it easier to use across tools that support the specification. Feel free to add any servers that may be hosting your API. In this example, I just added the default HTTPS URL.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "WebApplication10",
Version = "v1",
});
c.SwaggerGeneratorOptions.Servers = new List<OpenApiServer>()
{
// set the urls folks can reach server
new() {Url = "https://localhost:5001" }
};
});
}
Next, let’s change our Configure
method to serve static files and remove the SwaggerMiddleware
.
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseSwagger();
}
app.UseStaticFiles();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication10 v1"));
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
}
As you may have noticed, we commented out the call to UseSwagger
. The change takes the middleware out of the request pipeline and will no longer generate the swagger.json
or swagger.yaml
documents at runtime because we have them at build time.
Benefits Of Static OpenAPI Specifications
Like most static files, we get a lot of benefits from generating our OpenAPI specification:
- We can serve these files from a CDN, reducing the load on our server.
- We can share OpenAPI specifications with third parties.
- We can use OpenAPI specifications with different tooling (such as JetBrains Rider).
- No more runtime generation of the specification reduces application start-up time.
These are a few of the benefits I can think of, and reducing start-up time is a big concern for folks working in environments with slow cold-startup times.
Conclusion
While relatively simple, the approach to statically generating an OpenAPI specification has many benefits. .NET CLI tooling makes it reasonably straightforward to integrate into the build pipeline of a project. In addition, we’ve seen it take very little modification to serve static files, so it is an easy optimization for any ASP.NET Core web application.
I hope you found this post helpful, and thanks for reading. If you did find this post useful, please consider sharing it with other developers.