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 then use 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.