Compartir a través de


Personalización de documentos de OpenAPI

Transformadores de documentos de OpenAPI

Los transformadores proporcionan una API para modificar el documento de OpenAPI con personalizaciones definidas por el usuario. Los transformadores son útiles para escenarios como:

  • Agregar parámetros a todas las operaciones de un documento.
  • Modificar descripciones para parámetros u operaciones.
  • Agregar información de nivel superior al documento de OpenAPI.

Los transformadores se dividen en tres categorías:

  • Los transformadores de documentos tienen acceso a todo el documento de OpenAPI. Se pueden usar para realizar modificaciones globales en el documento.
  • Los transformadores de operación se aplican a cada operación individual. Cada operación individual es una combinación de ruta de acceso y método HTTP. Se pueden usar para modificar parámetros o respuestas en puntos de conexión.
  • Los transformadores de esquema se aplican a cada esquema del documento. Se pueden emplear para modificar el esquema de los cuerpos de solicitud o respuesta, o de cualquier esquema anidado.

Los transformadores se pueden registrar en el documento a través de la llamada al método AddDocumentTransformer en el objeto OpenApiOptions. En el fragmento de código siguiente se muestran diferentes formas de registrar transformadores en el documento:

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

var builder = WebApplication.CreateBuilder(args);

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();

Orden de ejecución para los transformadores

Los transformadores se ejecutan en el orden siguiente:

  • Los transformadores de esquema se ejecutan cuando se registra un esquema en el documento. Son ejecutados en el orden en que se agregan. Todos los esquemas se agregan al documento antes de que se produzca cualquier procesamiento de operaciones, por lo que los transformadores de esquema se ejecutan antes de los transformadores de operación.
  • Los transformadores de operación se ejecutan cuando se agrega una operación al documento. Son ejecutados en el orden en que se agregan. Todas las operaciones se agregan al documento antes de que se ejecuten los transformadores de documentos.
  • Los transformadores de documento se ejecutan cuando se genera el documento. Este es el último repaso del documento, y para este punto ya se han añadido todas las operaciones y esquemas.
  • Cuando una aplicación está configurada para generar varios documentos openAPI, los transformadores se ejecutan para cada documento de forma independiente.

Por ejemplo, en el siguiente fragmento de código:

  • SchemaTransformer2 se ejecuta y tiene acceso a las modificaciones realizadas por SchemaTransformer1.
  • Tanto OperationTransformer1 como OperationTransformer2 tienen acceso a las modificaciones realizadas por ambos transformadores de esquemas para los tipos implicados en la operación que se les llama a procesar.
  • OperationTransformer2 se ejecuta después de OperationTransformer1, por lo que tiene acceso a las modificaciones realizadas por OperationTransformer1.
  • Tanto DocumentTransformer1 como DocumentTransformer2 se ejecutan después de que se hayan agregado todas las operaciones y esquemas al documento, por lo que tienen acceso a todas las modificaciones realizadas por los transformadores de operación y esquema.
  • DocumentTransformer2 se ejecuta después de DocumentTransformer1, por lo que tiene acceso a las modificaciones realizadas por DocumentTransformer1.
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;

var builder = WebApplication.CreateBuilder(args);

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();

Uso de transformadores de documentos

Los transformadores de documentos tienen acceso a un objeto de contexto que incluye lo siguiente:

Los transformadores de documentos también pueden mutar el documento de OpenAPI que se genera. En el ejemplo siguiente se muestra un transformador de documentos que agrega información sobre la API al documento de OpenAPI.

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

var builder = WebApplication.CreateBuilder(args);

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();

Los transformadores de documentos activados por el servicio pueden usar instancias de DI para modificar la aplicación. En el ejemplo siguiente se muestra un transformador de documentos que usa el servicio IAuthenticationSchemeProvider de la capa de autenticación. Comprueba si se registran esquemas relacionados con el portador JWT en la aplicación y los agrega al nivel superior del documento de OpenAPI:

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

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 securitySchemes = new Dictionary<string, IOpenApiSecurityScheme>
            {
                ["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 = securitySchemes;
        }
    }
}

Los transformadores de documentos son únicos para la instancia de documento a la que están asociados. En el ejemplo siguiente, un transformador:

  • Registra los requisitos relacionados con la autenticación en el documento internal.
  • Deja sin modificar el documento public.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi;

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 securitySchemes = new Dictionary<string, IOpenApiSecurityScheme>
            {
                ["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 = securitySchemes;

            // Apply it as a requirement for all operations
            foreach (var operation in document.Paths.Values.SelectMany(path => path.Operations))
            {
                operation.Value.Security ??= [];
                operation.Value.Security.Add(new OpenApiSecurityRequirement
                {
                    [new OpenApiSecuritySchemeReference("Bearer", document)] = []
                });
            }
        }
    }
}

Uso de transformadores de operación

Las operaciones son combinaciones únicas de rutas de acceso y métodos HTTP en un documento de OpenAPI. Los transformadores de operación son útiles cuando una modificación:

  • Debe realizarse en cada punto de conexión de una aplicación o
  • Se aplica condicionalmente a determinadas rutas.

Los transformadores de operación tienen acceso a un objeto de contexto que contiene lo siguiente:

  • Nombre del documento al que pertenece la operación.
  • ApiDescription asociado con la operación.
  • El IServiceProvider usado en la generación de documentos.

Por ejemplo, el siguiente transformador de operación agrega 500 como código de estado de respuesta admitido por todas las operaciones del documento.

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

var builder = WebApplication.CreateBuilder();

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

builder.Services.AddOpenApi(options =>
{
    options.AddOperationTransformer((operation, context, cancellationToken) =>
    {
        operation.Responses ??= new OpenApiResponses();
        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();

Los transformadores de operación también se pueden agregar a un punto de conexión específico con la AddOpenApiOperationTransformer API, en lugar de todos los puntos de conexión de un documento. Esto puede ser útil para cambiar datos específicos de OpenAPI para un punto de conexión específico, como agregar un esquema de seguridad, una descripción de respuesta u otras propiedades de operación de OpenAPI. En el ejemplo siguiente se muestra cómo agregar un transformador de operación a un punto de conexión en desuso específicamente, que marca el punto de conexión como en desuso en el documento openAPI.

using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

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

app.MapGet("/old", () => "This endpoint is old and should not be used anymore")
.AddOpenApiOperationTransformer((operation, context, cancellationToken) =>
{
    operation.Deprecated = true;
    return Task.CompletedTask;
});

app.MapGet("/new", () => "This endpoint replaces /old");

app.Run();

Uso de transformadores de esquema

Los esquemas son los modelos de datos que se usan en los cuerpos de solicitud y respuesta de un documento de OpenAPI. Los transformadores de esquema son útiles cuando se realiza una modificación:

  • Debe hacerse en cada esquema del documento o
  • Se aplica condicionalmente a determinados esquemas.

Los transformadores de esquema tienen acceso a un objeto de contexto que contiene lo siguiente:

  • Nombre del documento al que pertenece el esquema.
  • La información de tipo JSON asociada al esquema de destino.
  • El IServiceProvider usado en la generación de documentos.

Por ejemplo, el siguiente transformador de esquema establece format de tipos decimales en decimal en lugar de 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; }
}

Compatibilidad con la generación de OpenApiSchemas en transformadores

Los desarrolladores pueden generar un esquema para un tipo de C# con la misma lógica que ASP.NET generación de documentos de OpenAPI Core y agregarlo al documento de OpenAPI. A continuación, se puede hacer referencia al esquema desde otro lugar del documento openAPI. Esta funcionalidad está disponible a partir de .NET 10.

El contexto pasado a los transformadores de documento, operación y esquema incluye un nuevo GetOrCreateSchemaAsync método que se puede usar para generar un esquema para un tipo. Este método también tiene un parámetro opcional ApiParameterDescription para especificar metadatos adicionales para el esquema generado.

Para admitir la adición del esquema al documento openAPI, se ha agregado una Document propiedad a los contextos de transformador de operación y esquema. Esto permite que cualquier transformador agregue un esquema al documento openAPI mediante el método del AddComponent documento.

Ejemplo

Para usar esta característica en un documento, operación o transformador de esquemas, cree el esquema mediante el GetOrCreateSchemaAsync método proporcionado en el contexto y agréguelo al documento OpenAPI mediante el AddComponent método del documento.

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)
                }
            }
        };
    });
});

Personalización de la reutilización del esquema

Una vez aplicados todos los transformadores, el marco revisa el documento para transferir determinados esquemas a la sección components.schemas y reemplazarlos por referencias $ref al esquema transferido. Esto reduce el tamaño del documento y facilita la lectura.

Los detalles de este procesamiento son complicados y pueden cambiar en versiones futuras de .NET, pero en general:

  • Los esquemas para los tipos de clase, registro o estructura se sustituyen por un esquema en $ref dentro de components.schemas si aparecen más de una vez en el documento.
  • Los esquemas para los tipos primitivos y las colecciones estándar se dejan insertados.
  • Los esquemas de los tipos de enumeración siempre se reemplazan con un $ref en un esquema de components.schemas.

Normalmente, el nombre del esquema en components.schemas es el nombre del tipo class/record/struct, pero en algunas circunstancias se debe usar un nombre diferente.

ASP.NET Core permite personalizar los esquemas que se reemplazan por un $ref en un esquema en components.schemas mediante la propiedad CreateSchemaReferenceId de OpenApiOptions. Esta propiedad es un delegado que toma un objeto JsonTypeInfo y devuelve el nombre del esquema en components.schemas que se debe usar para ese tipo. El marco proporciona una implementación predeterminada de este delegado, CreateDefaultSchemaReferenceId que usa el nombre del tipo, pero puede reemplazarlo por su propia implementación.

Como ejemplo sencillo de esta personalización, puede elegir siempre esquemas de enumeración insertados. Esto se hace estableciendo CreateSchemaReferenceId en un delegado que devuelve null para los tipos de enumeración y, de lo contrario, devuelve el valor de la implementación predeterminada. El código siguiente muestra esto:

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

Recursos adicionales

Transformadores de documentos de OpenAPI

Los transformadores proporcionan una API para modificar el documento de OpenAPI con personalizaciones definidas por el usuario. Los transformadores son útiles para escenarios como:

  • Agregar parámetros a todas las operaciones de un documento.
  • Modificar descripciones para parámetros u operaciones.
  • Agregar información de nivel superior al documento de OpenAPI.

Los transformadores se dividen en tres categorías:

  • Los transformadores de documentos tienen acceso a todo el documento de OpenAPI. Se pueden usar para realizar modificaciones globales en el documento.
  • Los transformadores de operación se aplican a cada operación individual. Cada operación individual es una combinación de ruta de acceso y método HTTP. Se pueden usar para modificar parámetros o respuestas en puntos de conexión.
  • Los transformadores de esquema se aplican a cada esquema del documento. Se pueden emplear para modificar el esquema de los cuerpos de solicitud o respuesta, o de cualquier esquema anidado.

Los transformadores se pueden registrar en el documento a través de la llamada al método AddDocumentTransformer en el objeto OpenApiOptions. En el fragmento de código siguiente se muestran diferentes formas de registrar transformadores en el documento:

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();

Orden de ejecución para los transformadores

Los transformadores se ejecutan de la siguiente manera:

  • Los transformadores de esquema se ejecutan cuando se registra un esquema en el documento. Los transformadores de esquema se ejecutan en el orden en que se añadieron. Todos los esquemas se agregan al documento antes de que se produzca cualquier procesamiento de operación, por lo que todos los transformadores de esquema se ejecutan antes de cualquier transformador de operación.
  • Los transformadores de operación se ejecutan cuando se agrega una operación al documento. Los transformadores de operación se ejecutan en el orden en que se agregaron. Todas las operaciones se agregan al documento antes de ejecutar cualquier transformador de documento.
  • Los transformadores de documentos se ejecutan cuando se genera el documento. Este es el paso final del documento y todas las operaciones y esquemas se han agregado en este punto.
  • Cuando una aplicación está configurada para generar varios documentos openAPI, los transformadores se ejecutan para cada documento de forma independiente.

Por ejemplo, en el siguiente fragmento de código:

  • SchemaTransformer2 se ejecuta y tiene acceso a las modificaciones realizadas por SchemaTransformer1.
  • Tanto OperationTransformer1 como OperationTransformer2 tienen acceso a las modificaciones realizadas por ambos transformadores de esquema para los tipos implicados en la operación a la que son llamados a procesar.
  • OperationTransformer2 se ejecuta después de OperationTransformer1, por lo que tiene acceso a las modificaciones realizadas por OperationTransformer1.
  • Tanto DocumentTransformer1 como DocumentTransformer2 se ejecutan después de que se hayan agregado todas las operaciones y esquemas al documento, por lo que tienen acceso a todas las modificaciones realizadas por los transformadores de operación y esquema.
  • DocumentTransformer2 se ejecuta después de DocumentTransformer1, por lo que tiene acceso a las modificaciones realizadas por 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();

Uso de transformadores de documentos

Los transformadores de documentos tienen acceso a un objeto de contexto que incluye lo siguiente:

Los transformadores de documentos también pueden mutar el documento de OpenAPI que se genera. En el ejemplo siguiente se muestra un transformador de documentos que agrega información sobre la API al documento de OpenAPI.

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();

Los transformadores de documentos activados por el servicio pueden usar instancias de DI para modificar la aplicación. En el ejemplo siguiente se muestra un transformador de documentos que usa el servicio IAuthenticationSchemeProvider de la capa de autenticación. Comprueba si se registran esquemas relacionados con el portador JWT en la aplicación y los agrega al nivel superior del documento de 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;
        }
    }
}

Los transformadores de documentos son únicos para la instancia de documento a la que están asociados. En el ejemplo siguiente, un transformador:

  • Registra los requisitos relacionados con la autenticación en el documento internal.
  • Deja sin modificar el documento 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>()
                });
            }
        }
    }
}

Uso de transformadores de operación

Las operaciones son combinaciones únicas de rutas de acceso y métodos HTTP en un documento de OpenAPI. Los transformadores de operación son útiles cuando una modificación:

  • Debe realizarse en cada punto de conexión de una aplicación o
  • Se aplica condicionalmente a determinadas rutas.

Los transformadores de operación tienen acceso a un objeto de contexto que contiene lo siguiente:

  • Nombre del documento al que pertenece la operación.
  • ApiDescription asociado con la operación.
  • El IServiceProvider usado en la generación de documentos.

Por ejemplo, el siguiente transformador de operación agrega 500 como código de estado de respuesta admitido por todas las operaciones del documento.

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();

Uso de transformadores de esquema

Los esquemas son los modelos de datos que se usan en los cuerpos de solicitud y respuesta de un documento de OpenAPI. Los transformadores de esquema son útiles cuando se realiza una modificación:

  • Debe hacerse en cada esquema del documento o
  • Se aplica condicionalmente a determinados esquemas.

Los transformadores de esquema tienen acceso a un objeto de contexto que contiene lo siguiente:

  • Nombre del documento al que pertenece el esquema.
  • La información de tipo JSON asociada al esquema de destino.
  • El IServiceProvider usado en la generación de documentos.

Por ejemplo, el siguiente transformador de esquema establece format de tipos decimales en decimal en lugar de 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; }
}

Personalización de la reutilización del esquema

Una vez aplicados todos los transformadores, el marco revisa el documento para transferir determinados esquemas a la sección components.schemas y reemplazarlos por referencias $ref al esquema transferido. Esto reduce el tamaño del documento y facilita la lectura.

Los detalles de este procesamiento son complicados y pueden cambiar en versiones futuras de .NET, pero en general:

  • Los esquemas para los tipos de clase, registro o estructura se sustituyen por un esquema en $ref dentro de components.schemas si aparecen más de una vez en el documento.
  • Los esquemas para los tipos primitivos y las colecciones estándar se dejan insertados.
  • Los esquemas de los tipos de enumeración siempre se reemplazan con un $ref en un esquema de components.schemas.

Normalmente, el nombre del esquema en components.schemas es el nombre del tipo class/record/struct, pero en algunas circunstancias se debe usar un nombre diferente.

ASP.NET Core permite personalizar los esquemas que se reemplazan por un $ref en un esquema en components.schemas mediante la propiedad CreateSchemaReferenceId de OpenApiOptions. Esta propiedad es un delegado que toma un objeto JsonTypeInfo y devuelve el nombre del esquema en components.schemas que se debe usar para ese tipo. El marco proporciona una implementación predeterminada de este delegado, CreateDefaultSchemaReferenceId que usa el nombre del tipo, pero puede reemplazarlo por su propia implementación.

Como ejemplo sencillo de esta personalización, puede elegir siempre esquemas de enumeración insertados. Esto se hace estableciendo CreateSchemaReferenceId en un delegado que devuelve null para los tipos de enumeración y, de lo contrario, devuelve el valor de la implementación predeterminada. En el código siguiente se muestra cómo hacerlo:

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

Recursos adicionales