OpenAPI v3 and Swagger UI

OpenAPI v3 and Swagger UI Background
6 min read

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.