1. Swagger Support

Swagger Support

Swagger support is provided via the excellent NSwag library. your mileage may vary since NSwag is presently tied closely to the MVC framework and support for .NET Minimal APIs is lacking in some areas.

If you find some rough edges with the Swagger support in FastEndpoints, please get in touch by creating a github issue or submit a pull request if you have experience dealing with Swagger.

Enable Swagger

First install the FastEndpoints.Swagger package and add 4 lines to your app startup:

Installation:

terminal
dotnet add package FastEndpoints.Swagger

Usage:

Program.cs
global using FastEndpoints;
using FastEndpoints.Swagger; //add this

var builder = WebApplication.CreateBuilder();
builder.Services.AddFastEndpoints();
builder.Services.AddSwaggerDoc(); //add this

var app = builder.Build();
app.UseAuthorization();
app.UseFastEndpoints();
app.UseOpenApi(); //add this
app.UseSwaggerUi3(s => s.ConfigureDefaults()); //add this
app.Run();

You can then visit /swagger or /swagger/v1/swagger.json to see Swagger output.

Configuration

Swagger options can be configured as you'd typically do via the AddSwaggerDoc() method:

Program.cs
builder.Services.AddSwaggerDoc(settings =>
{
    settings.Title = "My API";
    settings.Version = "v1";
});

Describe Endpoints

By default, both Accepts and Produces metadata are inferred from the request/response DTO types of your endpoints and added to the Swagger document automatically.

So, you only need to specify the additional accepts/produces metadata using the Description() method like so:

public class MyEndpoint : Endpoint<MyRequest, MyResponse>
{
    public override void Configure()
    {
        Post("/item/create");
        Description(b => b
          .Produces<ErrorResponse>(400, "application/json+problem")
          .ProducesProblemFE<InternalErrorResponse>(500)); //if using FE exception handler
    }
}

If the default Accepts & Produces is not to your liking, you can clear the defaults and do it all yourself by setting the clearDefaults argument to true:

public override void Configure()
{
    Post("/item/create");
    Description(b => b
        .Accepts<MyRequest>("application/json+custom")
        .Produces<MyResponse>(200, "application/json+custom")
        .ProducesProblemFE(400), //this is the same as .Produces<ErrorResponse>(400) above
        .ProducesProblemFE<InternalErrorResponse>(500),
    clearDefaults: true);
}

Swagger Documentation

Summary & description text of the different responses the endpoint returns, as well as an example request object can be specified with the Summary() method:

public override void Configure()
{
    Post("/item/create");
    Description(b => b.Produces(403));
    Summary(s => {
        s.Summary = "short summary goes here";
        s.Description = "long description goes here";
        s.ExampleRequest = new MyRequest { ... };
        s.Responses[200] = "ok response description goes here";
        s.Responses[403] = "forbidden response description goes here";
    });
}

If you prefer to move the summary text out of the endpoint class, you can do so by subclassing the EndpointSummary type:

class AdminLoginSummary : EndpointSummary
{
    public AdminLoginSummary()
    {
        Summary = "short summary goes here";
        Description = "long description goes here";
        ExampleRequest = new MyRequest { ... };
        Responses[200] = "success response description goes here";
        Responses[403] = "forbidden response description goes here";
    }
}

public override void Configure()
{
    Post("/admin/login");
    AllowAnonymous();
    Description(b => b.Produces(403));
    Summary(new AdminLoginSummary());
}

Alternatively, if you'd like to get rid of all traces of swagger documentation from your endpoint classes and have the summary completely separated, you can implement the Summary<TEndpoint> abstract class like shown below:

public class MySummary : Summary<MyEndpoint>
{
    public MySummary()
    {
        Summary = "short summary goes here";
        Description = "long description goes here";
        ExampleRequest = new MyRequest {...};
        Response<MyResponse>(200, "ok response with body");
        Response<ErrorResponse>(400, "validation failure");
        Response(404, "account not found");
    }
}

public class MyEndpoint : Endpoint<MyRequest, MyResponse>
{
    public override void Configure()
    {
        Post("/api/my-endpoint");
        //no need to specify summary here
    }
}

The Response() method above does the same job as the Produces() method mentioned earlier. Do note however, if you use the Response() method, the default 200 response is automatically removed, and you'd have to specify the 200 response yourself if it applies to your endpoint.

Describe Request Params

Route parameters, Query parameters and Request DTO property descriptions can be specified either with xml comments or with the Summary() method or EndpointSummary or Summary<TEndpoint,TRequest> subclassing.

Take the following for example:

Request.cs
/// <summary>
/// the admin login request summary
/// </summary>
public class Request
{
    /// <summary>
    /// username field description
    /// </summary>
    public string UserName { get; set; }

    /// <summary>
    /// password field description
    /// </summary>
    public string Password { get; set; }
}
Endpoint.cs
public override void Configure()
{
    Post("admin/login/{ClientID?}");
    AllowAnonymous();
    Summary(s =>
    {
        s.Summary = "summary";
        s.Description = "description";
        s.Params["ClientID"] = "client id description";
        s.RequestParam(r => r.UserName, "overriden username description");
    });
}

Use the s.Params dictionary to specify descriptions for params that don't exist on the request dto or when there is no request DTO.

Use the s.RequestParam() method to specify descriptions for properties of the request dto in a strongly-typed manner.

RequestParam() is also available when you use the Summary<TEndpoint,TRequest> generic overload.

Whatever you specify within the Summary() method as above takes higher precedence over XML comments.

Enabling XML Documentation

XML documentation is only supported for request/response DTOs (Swagger schemas) which can be enabled by adding the following to the csproj file:

Project.csproj
<PropertyGroup>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <NoWarn>CS1591</NoWarn>
</PropertyGroup>
NOTE

If you can figure out how to get NSwag to read the XML summary/remarks tags from the endpoint classes, please submit a PR on Github.

Adding Query Params To Swagger

In order to let Swagger know that a particular request DTO property is being bound from a query string parameter, you need to decorate that property with the [QueryParam] attribute like below.

When you annotate a property with the [QueryParam] attribute, a query parameter will be added to the Swagger document for that property.

CreateEmployeeRequest.cs
public class CreateEmployeeRequest
{
    [QueryParam]
    public string Name { get; set; } // bound from query string

    [QueryParam, BindFrom("id")]
    public string? ID { get; set; } // bound from query string

    public Address Address { get; set; } // bound from body
}

The [QueryParam] attribute does not affect the model binding order in any way. It is simply a way to make Swagger add a query param.

Specifying Default Values

Keeping in line with the NSwag convention, the default values for swagger is provided by decorating the request DTOs with the [DefaultValue(...)] attribute like so:

Request.cs
public class Request
{
    [DefaultValue("Admin")]
    public string UserName { get; set; }

    [DefaultValue("Qwerty321")]
    public string Password { get; set; }
}

Disable JWT Auth Scheme

Support for JWT Bearer Auth is automatically added. If you need to disable it, simply pass a false value to the following parameter:

Program.cs
builder.Services.AddSwaggerDoc(addJWTBearerAuth: false);

Multiple Authentication Schemes

Multiple global auth scheme support can be enabled by using the AddAuth() method like below.

Program.cs
builder.Services.AddSwaggerDoc(s =>
{
    s.DocumentName = "Release 1.0";
    s.Title = "Web API";
    s.Version = "v1.0";
    s.AddAuth("ApiKey", new()
    {
        Name = "api_key",
        In = OpenApiSecurityApiKeyLocation.Header,
        Type = OpenApiSecuritySchemeType.ApiKey,
    });
    s.AddAuth("Bearer", new()
    {
        Type = OpenApiSecuritySchemeType.Http,
        Scheme = "Bearer",
        BearerFormat = "JWT",
    });
});

Swagger Serializer Options

Even though NSwag uses a separate serializer (Newtonsoft) internally, we specify serialization settings for NSwag using System.Text.Json.JsonSerializerOptions just so we don't have to deal with anything related to Newtonsoft (until NSwag fully switches over to System.Text.Json).

builder.Services.AddSwaggerDoc(serializerSettings: x =>
{
    x.PropertyNamingPolicy = null;
    x.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
    ...
});

With the above approach, System.Text.Json annotations such as [JsonIgnore] and [JsonPropertyName] on your DTOs work out of the box.

Short Schema Names

The full name (including namespace) of the DTO classes are used to generate the swagger schema names by default. You can change it to use just the class names by doing the following at startup:

builder.Services.AddSwaggerDoc(shortSchemaNames: true);

Short Endpoint Names

The full name (including namespace) of the endpoint classes are used to generate the operation ids by default. You can change it to use just the class names by doing the following at startup:

Program.cs
app.UseFastEndpoints(c =>
{
    c.ShortEndpointNames = true;
});

Custom Endpoint Names

If the auto-generated operation ids are not to your liking, you can specify a name for an endpoint using the WithName() method.

Endpoint.cs
public override void Configure()
{
    Get("/sales/invoice/{InvoiceID}");
    Description(x => x.WithName("GetInvoice"));
}
INFO

When you manually specify a name for an endpoint like above and you want to point to that endpoint when using SendCreatedAtAsync() method, you must use the overload that takes a string argument with which you can specify the name of the target endpoint. I.e. you lose the convenience/type-safety of being able to simply point to another endpoint using the class type like so:

await SendCreatedAtAsync<GetInvoiceEndpoint>(...);

Instead you must do this:

await SendCreatedAtAsync("GetInvoice", ...);

© FastEndpoints 2022