OpenAPI v3 and Swagger UI
In the ServiceStack v8.1 release, we have introduced a way to better incorporate your ServiceStack APIs into the larger ASP.NET Core ecosystem by mapping your ServiceStack APIs to standard ASP.NET Core Endpoints. This enables your ServiceStack APIs integrate with your larger ASP.NET Core application in the same way other middleware does, opening up more opportunities for reuse of your ServiceStack APIs.
This opens up the ability to use common third party tooling. A good example of this is adding OpenAPI v3 specification
generation for your endpoints offered by the Swashbuckle.AspNetCore
package.
Included in the v8.1 Release is the ServiceStack.AspNetCore.OpenApi
package to make this integration
as easy as possible, and incorporate additional information from your ServiceStack APIs into Swagger metadata.
Previously, without the ability to map Endpoints, we've maintained a ServiceStack specific OpenAPI specification generation
via the OpenApiFeature
plugin. While this provided a lot of functionality by accurately describing your ServiceStack APIs,
it could be tricky to customize those API descriptions to the way some users wanted to.
In this post we will look at how you can take advantage of the new OpenAPI v3 Swagger support using mapped Endpoints, customizing the generated specification, as well as touch on other related changes to ServiceStack v8.1.
AppHost Initialization​
To use ServiceStack APIs as mapped Endpoints, the way ServiceStack is initialized in .
To convert your App to use Endpoint Routing and ASP.NET Core IOC your ASPNET Core
application needs to be updated to replace any usage of Funq
IoC container to use ASP.NET Core's IOC.
Previously, the following was used to initialize your ServiceStack AppHost
:
Program.cs​
app.UseServiceStack(new AppHost());
The app
in this example is a WebApplication
resulting from an IHostApplicationBuilder
calling builder.Build()
.
Whilst we still need to call app.UseServiceStack()
, we also need to move the discovery of your ServiceStack APIs to earlier
in the setup before the WebApplication
is built, e.g:
// Register ServiceStack APIs, Dependencies and Plugins:
services.AddServiceStack(typeof(MyServices).Assembly);
var app = builder.Build();
//...
// Register ServiceStack AppHost
app.UseServiceStack(new AppHost(), options => {
options.MapEndpoints();
});
app.Run();
Once configured to use Endpoint Routing we can the mix tool to apply the openapi3 Startup Configuration with:
x mix openapi3
Manually Configure OpenAPI v3 and Swagger UI​
This will install the required ASP.NET Core Microsoft, Swashbuckle and ServiceStack Open API NuGet packages:
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.*" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.*" />
<PackageReference Include="ServiceStack.AspNetCore.OpenApi" Version="8.*" />
Then add the Configure.OpenApi.cs
Modular Startup class to your project:
[assembly: HostingStartup(typeof(MyApp.ConfigureOpenApi))]
namespace MyApp;
public class ConfigureOpenApi : IHostingStartup
{
public void Configure(IWebHostBuilder builder) => builder
.ConfigureServices((context, services) =>
{
if (context.HostingEnvironment.IsDevelopment())
{
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(); // Swashbuckle
services.AddServiceStackSwagger();
services.AddBasicAuth<ApplicationUser>(); // Enable HTTP Basic Auth
//services.AddJwtAuth(); // Enable & Use JWT Auth
services.AddTransient<IStartupFilter, StartupFilter>();
}
});
public class StartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
=> app => {
// Provided by Swashbuckle library
app.UseSwagger();
app.UseSwaggerUI();
next(app);
};
}
}
All this setup is done for you in ServiceStack's updated Identity Auth .NET 8 Templates, but for existing applications, you will need to do convert to use Endpoint Routing to support this new way of running your ServiceStack applications.
More Control​
One point of friction with our previous OpenApiFeature
plugin was the missing customization ability to the OpenAPI spec to somewhat disconnect from the defined ServiceStack service, and related C# Request and Response Data Transfer Objects (DTOs). Since the OpenApiFeature
plugin used class and property attributes on your Request DTOs, making the structure of the OpenAPI schema mapping quite ridged, preventing the ability for certain customizations.
For example, if we have an UpdateTodo
Request DTO that looks like the following:
[Route("/todos/{Id}", "PUT")]
public class UpdateTodo : IPut, IReturn<Todo>
{
public long Id { get; set; }
[ValidateNotEmpty]
public string Text { get; set; }
public bool IsFinished { get; set; }
}
Previously, we would get a default Swagger UI that enabled all the properties as Paramters
to populate.
While this correctly describes the Request DTO structure, sometimes as developers we get requirements for how we want to present our APIs to our users from within the Swagger UI.
With the updated SwaggerUI, and the use of the Swashbuckle
library, we get the following UI by default.
These are essentially the same, we have a CRUD Todo API that takes a UpdateTodo
Request DTO, and returns a Todo
Response DTO. ServiceStack needs to have uniquely named Request DTOs, so we can't have a Todo
schema as the Request DTO despite the fact that it is the same structure as our Todo
model.
This is a good thing, as it allows us to have a clean API contract, and separation of concerns between our Request DTOs and our models.
However, it might not be desired to present this to our users, since it can be convenient to think about CRUD services as taking the same resource type as the response.
To achieve this, we use the Swashbuckle library to customize the OpenAPI spec generation. Depending on what you want to customize, you can use the SchemaFilter
or OperationFilter
options. In this case, we want to customize the matching operation to reference the Todo
schema for the Request Body.
First, we create a new class that implements the IOperationFilter
interface.
public class OperationRenameFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (context.ApiDescription.HttpMethod == "PUT" &&
context.ApiDescription.RelativePath == "todos/{Id}")
{
operation.RequestBody.Content["application/json"].Schema.Reference =
new OpenApiReference {
Type = ReferenceType.Schema,
Id = "Todo"
};
}
}
}
The above matches some information about the UpdateTodo
request we want to customize, and then sets the Reference
property of the RequestBody
to the Todo
schema.
We can then add this to the AddSwaggerGen
options in the Program.cs
file.
builder.Services.AddSwaggerGen(o =>
{
o.OperationFilter<OperationRenameFilter>();
});
The result is the following Swagger UI.
This is just one simple example of how you can customize the OpenAPI spec generation, and Swashbuckle
has some great documentation on the different ways you can customize the generated spec.
And these customizations impact any of your ASP.NET Core Endpoints, not just your ServiceStack APIs.
Closing​
Now that ServiceStack APIs can be mapped to standard ASP.NET Core Endpoints, it opens up a lot of possibilities for integrating your ServiceStack APIs into the larger ASP.NET Core ecosystem.
The use of the Swashbuckle
library via the ServiceStack.AspNetCore.OpenApi
library is just one example of how you can take advantage of this new functionality.