Open API v3

INFO

For documentation on using OpenApiFeature refer to Open API v2 instead

Utilizing the same ASP.NET Core Endpoint Routing that the rest of the ASP.NET Core App uses enables your ServiceStack APIs to integrate with your wider ASP.NET Core application, opening up more opportunities for greater reuse.

This opens up the ability to use common third party tooling to implement application-wide features like OpenAPI v3 specification generation support for your endpoints offered by the Swashbuckle.AspNetCore package.

To make this integration as easy as possible we're maintaining the ServiceStack.AspNetCore.OpenApi package to incorporate additional information from your ServiceStack APIs into Swagger metadata.

In this guide 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 when using Endpoint Routing.

AppHost Initialization

To use Swashbuckle your ServiceStack App needs to be configured to use Endpoint Routing and ASP.NET Core IOC where registration of your ServiceStack Services and App's Dependencies & Plugins need to be setup before your WebApplication is built, e.g:

Program.cs

// 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 our updated Identity Auth .NET 8 Templates.

Open API Attributes

Each route could have a separate summary and description. You can set it with Route attribute:

[Route("/hello", Summary = @"Default hello service.", 
    Notes = "Longer description for hello service.")]

You can set specific description for each HTTP method like shown below:

[Route("/hello/{Name}", "GET", Summary="Says 'Hello' to provided Name", 
    Notes = "Longer description of the GET method which says 'Hello'")]
[Route("/hello/{Name}", "POST", Summary="Says 'Hello' to provided Name", 
    Notes = "Longer description of the POST method which says 'Hello'")]

You can also document your services in the OpenAPI with the new [Api] and [ApiMember] annotation attributes, e,g: Here's an example of a fully documented service:

[Api("Service Description")]
[Tag("Core Requests")]
[ApiResponse(HttpStatusCode.BadRequest, "Your request was not understood")]
[ApiResponse(HttpStatusCode.InternalServerError, "Oops, something broke")]
[Route("/swagger/{Name}", "GET", Summary = "GET Summary", Notes = "Notes")]
[Route("/swagger/{Name}", "POST", Summary = "POST Summary", Notes="Notes")]
public class MyRequestDto
{
    [ApiMember(Name="Name", Description = "Name Description",
        ParameterType = "path", DataType = "string", IsRequired = true)]
    [ApiAllowableValues("Name", typeof(Color))] //Enum
    public string Name { get; set; }
}

Please note, that if you used ApiMember.DataType for annotating OpenApiFeature then you need to change the types to OpenAPI type when migrating to OpenApiFeature. For example, annotation of

[ApiMember(DataType="int")]

need to be changed to

[ApiMember(DataType="integer", Format="int32")]

Here is the table for type migration

Swagger Type (DataType) OpenAPI Type (DataType) OpenAPI Format (Format)
Array array
boolean boolean
byte integer int
Date string date
string date-time
double number double
float number float
int integer int32
long integer int64
string string

You can use [ApiAllowableValues] lets you anotate enum properties as well as a restriction for values in array, e.g:

[ApiAllowableValues("Includes", Values = new string[] { "Genres", "Releases", "Contributors" })]
public string[] Includes { get; set; }

INFO

The use of ApiMember turns your DTO properties as opt-in only for OpenApi metadata. Meaning that only properties annotated with [ApiMember] will be included in the OpenApi metadata for classes that use [ApiMember] on any of its properties.

Group APIs with Tags

You can tag the DTO with [Tag] attribute. Attributes are annotated by the same tag are grouped by the tag name in Swagger UI. DTOs can have multiple tags, e.g:

[Tag("Core Features")]
[Tag("Scheduler")]
public class MyRequest { ... }

You can Exclude properties from being listed in OpenAPI with:

[IgnoreDataMember]

Exclude properties from being listed in OpenAPI Schema Body with:

[ApiMember(ExcludeInSchema=true)]

Exclude Services from Metadata Pages

To exclude entire Services from showing up in OpenAPI or any other Metadata Services (i.e. Metadata Pages, Postman, NativeTypes, etc), annotate Request DTO's with:

[ExcludeMetadata]
public class MyRequestDto { ... }

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 DTOs since it used Request DTOs property attributes 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 class:

[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.

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.