Skip to content

AspNetCore Middleware

Rico Suter edited this page Aug 30, 2022 · 54 revisions
  • Package: NSwag.AspNetCore
    • .NETStandard 1.6+, .NET Standard 2.0, .NET Core and .NET 4.5.1+
    • Supports ASP.NET Core 2.1 - 5.0

The NuGet package provides extension methods to register the NSwag ASP.NET Core services and middlewares:

The middlewares are based on the AspNetCoreOpenApiDocumentGenerator class which generates OpenAPI/Swagger specifications from ASP.NET Core controllers with the ASP.NET Core API Explorer.

To use the middlewares, open Startup.cs and add the following code to ConfigureServices() (full samples below):

  • services.AddOpenApiDocument(configure) (OpenAPI v3.0.0)
  • services.AddSwaggerDocument(configure) (Swagger v2.0): Adds a new OpenAPI/Swagger document registration to the DI system
    • The default document name is v1
    • The document instance implements the AspNetCoreOpenApiDocumentGeneratorSettings
    • To configure multiple documents, just call the methods multiple times and define a DocumentName per document:
services.AddOpenApiDocument(document => document.DocumentName = "a");
services.AddSwaggerDocument(document => document.DocumentName = "b");

In Startup.Configure():

  • app.UseOpenApi(configure): Adds the OpenAPI/Swagger document generator middleware (serves openapi.json/swagger.json).
    • The default route is /swagger/{documentName}/swagger.json; when using the {documentName} placeholder in the Path setting, multiple documents can be served
  • app.UseSwaggerUi3(configure):
  • app.UseReDoc(configure): Adds only the Swagger UI or ReDoc web UI
    • The default UI route in Path is /swagger which - in the case of Swagger UI - serves one UI with all available documents
    • The default document route in DocumentPath is /swagger/{documentName}/swagger.json
app.UseOpenApi(); // serve documents (same as app.UseSwagger())
app.UseSwaggerUi3(); // serve Swagger UI
app.UseReDoc(); // serve ReDoc UI

Sample projects:

Usage

Install required NuGet packages

First, you need to install the required NSwag NuGet packages.

Register the middleware

public class Startup
{
    ...

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
            .AddJsonOptions(options =>
            {
                // Use camel case properties in the serializer and the spec (optional)
                options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                // Use string enums in the serializer and the spec (optional)
                options.SerializerSettings.Converters.Add(new StringEnumConverter());
            });

        // Add OpenAPI/Swagger document
        services.AddOpenApiDocument(); // registers a OpenAPI v3.0 document with the name "v1" (default)
        // services.AddSwaggerDocument(); // registers a Swagger v2.0 document with the name "v1" (default)
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvc();
        
        // Add OpenAPI/Swagger middlewares
        app.UseOpenApi(); // Serves the registered OpenAPI/Swagger documents by default on `/swagger/{documentName}/swagger.json`
        app.UseSwaggerUi3(); // Serves the Swagger UI 3 web ui to view the OpenAPI/Swagger documents by default on `/swagger`
        
        // Register the middleware before UseRouting()
        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }

Customization

The AspNetCoreOpenApiDocumentGenerator class provides lots of customization and extension possibilities.

Use API Versioning

Use the ApiGroupNames setting to select the operation version filter:

services.AddOpenApiDocument(document => 
{
    document.DocumentName = "v1";
    document.ApiGroupNames = new [] { "1" };
});
services.AddOpenApiDocument(document => 
{
    document.DocumentName = "v2";
    document.ApiGroupNames = new [] { "2" };
});

The API versioning has to be configured like this:

services.AddApiVersioning(options =>
{
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ApiVersionReader = new UrlSegmentApiVersionReader();
})
.AddMvcCore()
.AddVersionedApiExplorer(options =>
{
    options.GroupNameFormat = "VVV";
    options.SubstituteApiVersionInUrl = true;
});

For more information have a look at this PR.

For more information on the GroupNameFormat property, have a look at this comment.

Post process the served OpenAPI/Swagger specification document

You can post process a generated document with the PostProcess setting from AddSwaggerDocument() or UseOpenApi() or use a custom Document Processor or Operation Processor (recommended).

services.AddSwaggerDocument(document =>
{
    document.PostProcess = d => 
    {
        d.Info.Title = "Hello world!";
    };
});

Caution: Only use PostProces from UseOpenApi() to transform the document related to the request. Other modifications should be done with a custom Document Processor or Operation Processor which is added in AddSwaggerDocument(document => document.DocumentProcessors) or AddSwaggerDocument(document => document.PostProcess = ...). The post processing in UseOpenApi() will not be called from the NSwag CLI or MSBuild, i.e. only when served via HTTP!

app.UseOpenApi(settings =>
{
    settings.PostProcess = (document, request) => 
    {
        document.Host = request...
    };
});

The PostProcess and TransformToExternalPath config might be needed when serving through a reverse proxy. See sample:

Generate multiple OpenAPI specifications

See sample project: Advanced Config (.NET Core 2.0)

Enable authentication in generator and Swagger UI

TODO: Add PKCE Auth: https://github.com/RicoSuter/NSwag/issues/2375#issuecomment-554394786

Add OAuth2 authentication (OpenAPI 3)

Ensure that the page 'oauth2-redirect.html' is allowed as Redirect URL on the Swagger UI route (e.g. https://localhost:5001/swagger/oauth2-redirect.html).

// Add security definition and scopes to document
services.AddOpenApiDocument(document =>
{
    document.AddSecurity("bearer", Enumerable.Empty<string>(), new OpenApiSecurityScheme
    {
        Type = OpenApiSecuritySchemeType.OAuth2,
        Description = "My Authentication",
        Flow = OpenApiOAuth2Flow.Implicit,
        Flows = new OpenApiOAuthFlows()
        {
            Implicit = new OpenApiOAuthFlow()
            {
                Scopes = new Dictionary<string, string>
                {
                    { "read", "Read access to protected resources" },
                    { "write", "Write access to protected resources" }
                },
                AuthorizationUrl = "https://localhost:44333/core/connect/authorize",
                TokenUrl = "https://localhost:44333/core/connect/token"
            },
        }
    });

    document.OperationProcessors.Add(
        new AspNetCoreOperationSecurityScopeProcessor("bearer"));
//      new OperationSecurityScopeProcessor("bearer"));
);

Define the configuration for the Swagger UI 3's OAuth2 client in Startup.Configure():

app.UseSwagger();
app.UseSwaggerUi3(settings =>
{
    settings.OAuth2Client = new OAuth2ClientSettings
    {
        ClientId = "foo",
        ClientSecret = "bar",
        AppName = "my_app",
        Realm = "my_realm",
        AdditionalQueryStringParameters =
        {
            { "foo", "bar" }
        }
    };
});
  • OAuth2Client: Defines the settings for the OAuth2 client (i.e. the Swagger UI frontend)
  • SecurityDefinitionAppender: Adds a security definition to the OpenAPI specification
  • OperationSecurityScopeProcessor (Reflection based): Scans the AuthorizeAttribute attributes on controllers and action methods and adds the given roles as security scopes to the OpenAPI specification
  • AspNetCoreOperationSecurityScopeProcessor (ASP.NET Core metadata based): For ASP.NET Core 2.2+ it is recommended to use the AspNetCoreOperationSecurityScopeProcessor instead of the OperationSecurityScopeAppender as it will use the metadata exposed by ASP.NET Core and should produce better results.

Enable API Key authentication

services.AddOpenApiDocument(document => 
{
    document.AddSecurity("apikey", Enumerable.Empty<string>(), new OpenApiSecurityScheme
    {
        Type = OpenApiSecuritySchemeType.ApiKey,
        Name = "api_key",
        In = OpenApiSecurityApiKeyLocation.Header
    });

    document.OperationProcessors.Add(
        new AspNetCoreOperationSecurityScopeProcessor("apikey"));
//      new OperationSecurityScopeProcessor("apikey"));
});

Enable manual JWT token authentication

You achieve this by telling NSwag to use JWT token authorization.

This applies the security scheme to the whole document:

services.AddMvc();
services.AddOpenApiDocument(document =>
{
    document.AddSecurity("Bearer", Enumerable.Empty<string>(), new OpenApiSecurityScheme
    {
        Type = OpenApiSecuritySchemeType.Http,
        Scheme = JwtBearerDefaults.AuthenticationScheme,
        BearerFormat = "JWT", 
        Description = "Type into the textbox: {your JWT token}."
    });

    document.OperationProcessors.Add(
        new AspNetCoreOperationSecurityScopeProcessor("Bearer"));
//      new OperationSecurityScopeProcessor("Bearer"));
});

As an alternative, this applies the security scheme only to the specific operations that require it:

services.AddOpenApiDocument(document =>
{
    document.AddSecurity("Bearer", new OpenApiSecurityScheme
    {
        Type = OpenApiSecuritySchemeType.Http,
        Scheme = JwtBearerDefaults.AuthenticationScheme,
        BearerFormat = "JWT", 
        Description = "Type into the textbox: {your JWT token}."
    });
    document.OperationProcessors.Add(new AspNetCoreOperationSecurityScopeProcessor("Bearer"));
});

Hack: Ask user for client ID and client secret

To let the user enter the client ID and client secret, use the following code for now:

new OAuth2ClientSettings
{
    ClientId = "\" + prompt('Please enter ClientId: ') + \"",
    ClientSecret = "\" + prompt('Please enter ClientSecret: ') + \""
}

Hosting behind a reverse proxy or as an IIS virtual application

v13 automatically handles common proxy routes, paths and headers, see this PR

When the application is hosted as an IIS virtual application or behind a (reverse) proxy, the request path to swagger.json is missing the path of the virtual application. For example, https://example.com/myapi/swagger will make a request to https://example.com/swagger/v1/swagger.json

To work around this, the request can be intercepted to modify the path, as demonstrated in the Reverse Proxy Config (.NET Core 2.1) sample project.

app.UseSwaggerUi3(config => config.TransformToExternalPath = (internalUiRoute, request) =>
{
    if (internalUiRoute.StartsWith("/") == true && internalUiRoute.StartsWith(request.PathBase) == false)
    {
        return request.PathBase + internalUiRoute;
    }
    else
    {
        return internalUiRoute;
    }
});

Also see: https://github.com/RicoSuter/NSwag/pull/2196

Generate specification via CLI

Run a customized build and select the assembly in NSwagStudio, create an nswag.json and execute it via CLI (nswag run nswag.json:

For more info, see CommandLine or MSBuild.

Generate specification in YAML format

app.UseOpenApi(p => p.Path = "/swagger/{documentName}/swagger.yaml");
app.UseSwaggerUi3(p => p.DocumentPath = "/swagger/{documentName}/swagger.yaml");
Clone this wiki locally