自定义 OpenAPI 文档

OpenAPI 文档转换器

转换器提供了一个 API,用于使用用户定义的自定义项修改 OpenAPI 文档。 变压器适用于以下场景:

  • 向文档中的所有操作添加参数。
  • 修改参数或操作的说明。
  • 将顶级信息添加到 OpenAPI 文档。

转换器分为三类:

  • 文档转换器有权访问整个 OpenAPI 文档。 这些可用于对文档进行全局修改。
  • 操作转换器适用于每个单独的操作。 每个单独的操作都是路径和 HTTP 方法的组合。 这些可用于修改终结点上的参数或响应。
  • 架构转换器适用于文档中的每个架构。 这些可用于修改请求或响应正文的架构或任何嵌套架构。

可以通过对 AddDocumentTransformer 对象调用 OpenApiOptions 方法将转换器注册到文档。 以下代码片段演示了将转换器注册到文档的不同方法:

using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, cancellationToken)
                             => Task.CompletedTask);
    options.AddDocumentTransformer(new MyDocumentTransformer());
    options.AddDocumentTransformer<MyDocumentTransformer>();
    options.AddOperationTransformer((operation, context, cancellationToken)
                            => Task.CompletedTask);
    options.AddOperationTransformer(new MyOperationTransformer());
    options.AddOperationTransformer<MyOperationTransformer>();
    options.AddSchemaTransformer((schema, context, cancellationToken)
                            => Task.CompletedTask);
    options.AddSchemaTransformer(new MySchemaTransformer());
    options.AddSchemaTransformer<MySchemaTransformer>();
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/", () => "Hello world!");

app.Run();

转换器的执行顺序

转换器按以下顺序执行:

  • 架构转换器在向文档注册架构时执行。 它们按添加的顺序执行。 在任何操作处理发生之前,所有架构都会添加到文档中,因此架构转换器在操作转换器之前执行。
  • 操作变压器在将操作添加到文档时执行。 它们按添加的顺序执行。 在执行任何文档转换器之前,所有操作都会添加到文档中。
  • 文档转换器在生成文档时执行。 这是文档的最终审阅,此时会添加所有操作和模式。
  • 将应用配置为生成多个 OpenAPI 文档时,转换器会单独为每个文档执行。

例如,在以下代码片段中:

  • SchemaTransformer2 被执行,并可以访问 SchemaTransformer1 所做的修改。
  • OperationTransformer1OperationTransformer2 均有权访问两个架构转换器对它们被调用来处理的操作中涉及类型所做的修改。
  • OperationTransformer2OperationTransformer1 之后执行,因此它可以访问由 OperationTransformer1 所做的修改。
  • DocumentTransformer1DocumentTransformer2 都是在所有操作和架构添加到文档后执行的,因此它们能够访问操作和架构转换器所做的所有修改。
  • DocumentTransformer2DocumentTransformer1 之后执行,因此它可以访问由 DocumentTransformer1 所做的修改。
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer<DocumentTransformer1>();
    options.AddSchemaTransformer<SchemaTransformer1>();
    options.AddDocumentTransformer<DocumentTransformer2>();
    options.AddOperationTransformer<OperationTransformer1>();
    options.AddSchemaTransformer<SchemaTransformer2>();
    options.AddOperationTransformer<OperationTransformer2>();
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/", () => "Hello world!");

app.Run();

使用文档转换器

文档转换器有权访问上下文对象,其中包括:

文档转换器还可以转换已生成的 OpenAPI 文档。 以下示例演示一个文档转换器,该转换器向 OpenAPI 文档添加有关 API 的一些信息。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, cancellationToken) =>
    {
        document.Info = new()
        {
            Title = "Checkout API",
            Version = "v1",
            Description = "API for processing checkouts from cart."
        };
        return Task.CompletedTask;
    });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/", () => "Hello world!");

app.Run();

服务激活的文档转换器可以利用 DI 中的实例来修改应用。 以下示例演示了使用身份验证层中的 IAuthenticationSchemeProvider 服务的文档转换器。 它会检查应用中是否注册了任何与 JWT 持有者相关的方案,并将其添加到 OpenAPI 文档的顶层:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/", () => "Hello world!");

app.Run();

internal sealed class BearerSecuritySchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer
{
    public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
    {
        var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
        if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
        {
            var requirements = new Dictionary<string, OpenApiSecurityScheme>
            {
                ["Bearer"] = new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.Http,
                    Scheme = "bearer", // "bearer" refers to the header name here
                    In = ParameterLocation.Header,
                    BearerFormat = "Json Web Token"
                }
            };
            document.Components ??= new OpenApiComponents();
            document.Components.SecuritySchemes = requirements;
        }
    }
}

文档转换器对于与之关联的文档实例是唯一的。 在下面的示例中,转换器:

  • internal 文档注册与身份验证相关的要求。
  • 不修改 public 文档。
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();

builder.Services.AddOpenApi("internal", options =>
{
    options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});
builder.Services.AddOpenApi("public");

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/world", () => "Hello world!")
    .WithGroupName("internal");
app.MapGet("/", () => "Hello universe!")
    .WithGroupName("public");

app.Run();

internal sealed class BearerSecuritySchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer
{
    public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
    {
        var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
        if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
        {
            // Add the security scheme at the document level
            var requirements = new Dictionary<string, OpenApiSecurityScheme>
            {
                ["Bearer"] = new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.Http,
                    Scheme = "bearer", // "bearer" refers to the header name here
                    In = ParameterLocation.Header,
                    BearerFormat = "Json Web Token"
                }
            };
            document.Components ??= new OpenApiComponents();
            document.Components.SecuritySchemes = requirements;

            // Apply it as a requirement for all operations
            foreach (var operation in document.Paths.Values.SelectMany(path => path.Operations))
            {
                operation.Value.Security.Add(new OpenApiSecurityRequirement
                {
                    [new OpenApiSecurityScheme { Reference = new OpenApiReference { Id = "Bearer", Type = ReferenceType.SecurityScheme } }] = Array.Empty<string>()
                });
            }
        }
    }
}

使用操作转换器

操作是 OpenAPI 文档中 HTTP 路径和方法的唯一组合。 当修改符合以下情况时,操作转换器非常有用:

  • 应该对应用中的每个终结点进行,或
  • 有条件地应用于某些路由。

操作转换器有权访问上下文对象,其中包括:

例如,以下操作转换器将 500 添加为文档中所有操作支持的响应状态代码。

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();

builder.Services.AddOpenApi(options =>
{
    options.AddOperationTransformer((operation, context, cancellationToken) =>
    {
        operation.Responses.Add("500", new OpenApiResponse { Description = "Internal server error" });
        return Task.CompletedTask;
    });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/", () => "Hello world!");

app.Run();

使用架构转换器

架构是在 OpenAPI 文档的请求和响应正文中使用的数据模型。 架构转换器在进行修改时非常有用:

  • 应针对文档中的每个架构进行设置,或
  • 有条件地应用于某些架构。

架构转换器有权访问上下文对象,其中包括:

  • 架构所属文档的名称。
  • 与目标架构关联的 JSON 类型信息。
  • 文档生成中使用的 IServiceProvider

例如,以下架构转换器将十进制类型的 format 设置为 decimal 而不是 double

using Microsoft.AspNetCore.OpenApi;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options => {
    // Schema transformer to set the format of decimal to 'decimal'
    options.AddSchemaTransformer((schema, context, cancellationToken) =>
    {
        if (context.JsonTypeInfo.Type == typeof(decimal))
        {
            schema.Format = "decimal";
        }
        return Task.CompletedTask;
    });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/", () => new Body { Amount = 1.1m });

app.Run();

public class Body {
    public decimal Amount { get; set; }
}

支持在转换器中生成 OpenApiSchemas

开发人员可以使用与 ASP.NET Core OpenAPI 文档生成相同的逻辑为 C# 类型生成架构,并将其添加到 OpenAPI 文档。 然后,可以从 OpenAPI 文档中的其他地方引用架构。 此功能从 .NET 10 开始可用。

传递给文档、操作和架构转换器的上下文中包括一个可以用于为类型生成架构的新 GetOrCreateSchemaAsync 方法。 此方法还有一个可选 ApiParameterDescription 参数,用于为生成的架构指定其他元数据。

为了支持将架构添加到 OpenAPI 文档,已在操作和架构转换器上下文中增加了一个 Document 属性。 这允许任何转换器使用文档 AddComponent 的方法将架构添加到 OpenAPI 文档。

示例:

若要在文档、作或架构转换器中使用此功能,请使用 GetOrCreateSchemaAsync 上下文中提供的方法创建架构,并使用文档 AddComponent 的方法将其添加到 OpenAPI 文档中。

builder.Services.AddOpenApi(options =>
{
    options.AddOperationTransformer(async (operation, context, cancellationToken) =>
    {
        // Generate schema for error responses
        var errorSchema = await context.GetOrCreateSchemaAsync(typeof(ProblemDetails), null, cancellationToken);
        context.Document?.AddComponent("Error", errorSchema);

        operation.Responses ??= new OpenApiResponses();
        // Add a "4XX" response to the operation with the newly created schema
        operation.Responses["4XX"] = new OpenApiResponse
        {
            Description = "Bad Request",
            Content = new Dictionary<string, OpenApiMediaType>
            {
                ["application/problem+json"] = new OpenApiMediaType
                {
                    Schema = new OpenApiSchemaReference("Error", context.Document)
                }
            }
        };
    });
});

自定义架构重用

应用所有转换器后,框架会对文档进行处理,将某些架构转移到 components.schemas 部分,并用 $ref 对这些转移架构的引用进行替换。 这会减小文档的大小,并使阅读更加容易。

此处理的详细信息很复杂,在 .NET 的未来版本中可能会更改,但一般情况下:

  • 如果类/记录/结构类型的架构在文档中出现多次,则将其替换为 $ref 中架构的 components.schemas
  • 基元类型和标准集合的架构保持内联。
  • 枚举类型的架构总是被 components.schemas 中架构的 $ref 替换。

通常,架构的名称 components.schemas 是类/记录/结构类型的名称,但在某些情况下必须使用不同的名称。

ASP.NET Core 允许你自定义使用 $refcomponents.schemas 属性将哪些架构替换为 CreateSchemaReferenceId 中架构的 OpenApiOptions。 此属性是一个委托,它接受一个 JsonTypeInfo 对象,并返回应为该类型使用的在 components.schemas 中定义的架构名称。 该框架为此委托提供了一个默认实现,CreateDefaultSchemaReferenceId 使用了类型的名称,但您可以用自己的实现来替换它。

作为此自定义的一个简单示例,你可以选择始终将枚举模式内联。 这是通过将 CreateSchemaReferenceId 设置为一个委托来实现的,该委托对于枚举类型返回 null,否则返回默认实现中的值。 以下代码演示了这一点:

builder.Services.AddOpenApi(options =>
{
    // Always inline enum schemas
    options.CreateSchemaReferenceId = (type) =>
        type.Type.IsEnum ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type);
});

其他资源

OpenAPI 文档转换器

转换器提供了一个 API,用于使用用户定义的自定义项修改 OpenAPI 文档。 变压器适用于以下场景:

  • 向文档中的所有操作添加参数。
  • 修改参数或操作的说明。
  • 将顶级信息添加到 OpenAPI 文档。

转换器分为三类:

  • 文档转换器有权访问整个 OpenAPI 文档。 这些可用于对文档进行全局修改。
  • 操作转换器适用于每个单独的操作。 每个单独的操作都是路径和 HTTP 方法的组合。 这些可用于修改终结点上的参数或响应。
  • 架构转换器适用于文档中的每个架构。 这些可用于修改请求或响应正文的架构或任何嵌套架构。

可以通过对 AddDocumentTransformer 对象调用 OpenApiOptions 方法将转换器注册到文档。 以下代码片段演示了将转换器注册到文档的不同方法:

using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, cancellationToken)
                             => Task.CompletedTask);
    options.AddDocumentTransformer(new MyDocumentTransformer());
    options.AddDocumentTransformer<MyDocumentTransformer>();
    options.AddOperationTransformer((operation, context, cancellationToken)
                            => Task.CompletedTask);
    options.AddOperationTransformer(new MyOperationTransformer());
    options.AddOperationTransformer<MyOperationTransformer>();
    options.AddSchemaTransformer((schema, context, cancellationToken)
                            => Task.CompletedTask);
    options.AddSchemaTransformer(new MySchemaTransformer());
    options.AddSchemaTransformer<MySchemaTransformer>();
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/", () => "Hello world!");

app.Run();

转换器的执行顺序

转换器按如下所示执行:

  • 架构转换器在向文档注册架构时执行。 架构转换器按照添加顺序执行。 所有模式在进行任何操作处理之前都会添加到文档中,因此所有模式转换器都会在任何操作转换器之前执行。
  • 当操作被添加到文档中时,将执行操作转换器。 操作转换器按照添加顺序执行。 在执行任何文档转换器之前,所有操作都会添加到文档中。
  • 生成文档时执行文档转换器。 这是文档的最终传递,到目前为止,所有操作和架构都已添加。
  • 将应用配置为生成多个 OpenAPI 文档时,将单独为每个文档执行转换器。

例如,在以下代码片段中:

  • SchemaTransformer2 被执行,并可以访问 SchemaTransformer1 所做的修改。
  • OperationTransformer1OperationTransformer2都有权访问由两个架构转换器针对它们被调用以处理的操作中涉及的类型所进行的修改。
  • OperationTransformer2OperationTransformer1 之后执行,因此它可以访问由 OperationTransformer1 所做的修改。
  • DocumentTransformer1DocumentTransformer2 都是在所有操作和架构添加到文档后执行的,因此它们能够访问操作和架构转换器所做的所有修改。
  • DocumentTransformer2DocumentTransformer1 之后执行,因此它可以访问由 DocumentTransformer1 所做的修改。
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer<DocumentTransformer1>();
    options.AddSchemaTransformer<SchemaTransformer1>();
    options.AddDocumentTransformer<DocumentTransformer2>();
    options.AddOperationTransformer<OperationTransformer1>();
    options.AddSchemaTransformer<SchemaTransformer2>();
    options.AddOperationTransformer<OperationTransformer2>();
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/", () => "Hello world!");

app.Run();

使用文档转换器

文档转换器有权访问上下文对象,其中包括:

文档转换器还可以转换已生成的 OpenAPI 文档。 以下示例演示一个文档转换器,该转换器向 OpenAPI 文档添加有关 API 的一些信息。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, cancellationToken) =>
    {
        document.Info = new()
        {
            Title = "Checkout API",
            Version = "v1",
            Description = "API for processing checkouts from cart."
        };
        return Task.CompletedTask;
    });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/", () => "Hello world!");

app.Run();

服务激活的文档转换器可以利用 DI 中的实例来修改应用。 以下示例演示了使用身份验证层中的 IAuthenticationSchemeProvider 服务的文档转换器。 它会检查应用中是否注册了任何与 JWT 持有者相关的方案,并将其添加到 OpenAPI 文档的顶层:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/", () => "Hello world!");

app.Run();

internal sealed class BearerSecuritySchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer
{
    public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
    {
        var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
        if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
        {
            var requirements = new Dictionary<string, OpenApiSecurityScheme>
            {
                ["Bearer"] = new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.Http,
                    Scheme = "bearer", // "bearer" refers to the header name here
                    In = ParameterLocation.Header,
                    BearerFormat = "Json Web Token"
                }
            };
            document.Components ??= new OpenApiComponents();
            document.Components.SecuritySchemes = requirements;
        }
    }
}

文档转换器对于与之关联的文档实例是唯一的。 在下面的示例中,转换器:

  • internal 文档注册与身份验证相关的要求。
  • 不修改 public 文档。
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();

builder.Services.AddOpenApi("internal", options =>
{
    options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});
builder.Services.AddOpenApi("public");

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/world", () => "Hello world!")
    .WithGroupName("internal");
app.MapGet("/", () => "Hello universe!")
    .WithGroupName("public");

app.Run();

internal sealed class BearerSecuritySchemeTransformer(IAuthenticationSchemeProvider authenticationSchemeProvider) : IOpenApiDocumentTransformer
{
    public async Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
    {
        var authenticationSchemes = await authenticationSchemeProvider.GetAllSchemesAsync();
        if (authenticationSchemes.Any(authScheme => authScheme.Name == "Bearer"))
        {
            // Add the security scheme at the document level
            var requirements = new Dictionary<string, OpenApiSecurityScheme>
            {
                ["Bearer"] = new OpenApiSecurityScheme
                {
                    Type = SecuritySchemeType.Http,
                    Scheme = "bearer", // "bearer" refers to the header name here
                    In = ParameterLocation.Header,
                    BearerFormat = "Json Web Token"
                }
            };
            document.Components ??= new OpenApiComponents();
            document.Components.SecuritySchemes = requirements;

            // Apply it as a requirement for all operations
            foreach (var operation in document.Paths.Values.SelectMany(path => path.Operations))
            {
                operation.Value.Security.Add(new OpenApiSecurityRequirement
                {
                    [new OpenApiSecurityScheme { Reference = new OpenApiReference { Id = "Bearer", Type = ReferenceType.SecurityScheme } }] = Array.Empty<string>()
                });
            }
        }
    }
}

使用操作转换器

操作是 OpenAPI 文档中 HTTP 路径和方法的唯一组合。 当修改符合以下情况时,操作转换器非常有用:

  • 应该对应用中的每个终结点进行,或
  • 有条件地应用于某些路由。

操作转换器有权访问上下文对象,其中包括:

例如,以下操作转换器将 500 添加为文档中所有操作支持的响应状态代码。

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;

var builder = WebApplication.CreateBuilder();

builder.Services.AddAuthentication().AddJwtBearer();

builder.Services.AddOpenApi(options =>
{
    options.AddOperationTransformer((operation, context, cancellationToken) =>
    {
        operation.Responses.Add("500", new OpenApiResponse { Description = "Internal server error" });
        return Task.CompletedTask;
    });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/", () => "Hello world!");

app.Run();

使用架构转换器

架构是在 OpenAPI 文档的请求和响应正文中使用的数据模型。 架构转换器在进行修改时非常有用:

  • 应针对文档中的每个架构进行设置,或
  • 有条件地应用于某些架构。

架构转换器有权访问上下文对象,其中包括:

  • 架构所属文档的名称。
  • 与目标架构关联的 JSON 类型信息。
  • 文档生成中使用的 IServiceProvider

例如,以下架构转换器将十进制类型的 format 设置为 decimal 而不是 double

using Microsoft.AspNetCore.OpenApi;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options => {
    // Schema transformer to set the format of decimal to 'decimal'
    options.AddSchemaTransformer((schema, context, cancellationToken) =>
    {
        if (context.JsonTypeInfo.Type == typeof(decimal))
        {
            schema.Format = "decimal";
        }
        return Task.CompletedTask;
    });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/", () => new Body { Amount = 1.1m });

app.Run();

public class Body {
    public decimal Amount { get; set; }
}

自定义架构重用

应用所有转换器后,框架会对文档进行处理,将某些架构转移到 components.schemas 部分,并用 $ref 对这些转移架构的引用进行替换。 这会减小文档的大小,并使阅读更加容易。

此处理的详细信息很复杂,在 .NET 的未来版本中可能会更改,但一般情况下:

  • 如果类/记录/结构类型的架构在文档中出现多次,则将其替换为 $ref 中架构的 components.schemas
  • 基元类型和标准集合的架构保持内联。
  • 枚举类型的架构总是被 components.schemas 中架构的 $ref 替换。

通常,架构的名称 components.schemas 是类/记录/结构类型的名称,但在某些情况下必须使用不同的名称。

ASP.NET Core 允许你自定义使用 $refcomponents.schemas 属性将哪些架构替换为 CreateSchemaReferenceId 中架构的 OpenApiOptions。 此属性是一个委托,它接受一个 JsonTypeInfo 对象,并返回应为该类型使用的在 components.schemas 中定义的架构名称。 该框架为此委托提供了一个默认实现,CreateDefaultSchemaReferenceId 使用了类型的名称,但您可以用自己的实现来替换它。

作为此自定义的一个简单示例,你可以选择始终将枚举模式内联。 这是通过将 CreateSchemaReferenceId 设置为一个委托来实现的,该委托对于枚举类型返回 null,否则返回默认实现中的值。 以下代码演示如何执行此作:

builder.Services.AddOpenApi(options =>
{
    // Always inline enum schemas
    options.CreateSchemaReferenceId = (type) =>
        type.Type.IsEnum ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type);
});

其他资源