Condividi tramite


Personalizzare documenti OpenAPI

Trasformatori di documento OpenAPI

I trasformatori forniscono un'API per la modifica del documento OpenAPI con personalizzazioni definite dall'utente. I trasformatori sono utili per scenari come:

  • Aggiunta di parametri a tutte le operazioni in un documento.
  • Modifica delle descrizioni per parametri o operazioni.
  • Aggiunta di informazioni di livello superiore al documento OpenAPI.

I trasformatori rientrano in tre categorie:

  • I trasformatori di documento hanno accesso all'intero documento OpenAPI. Questi possono essere usati per apportare modifiche globali al documento.
  • I trasformatori di operazione si applicano a ogni singola operazione. Ogni singola operazione è una combinazione di percorso e metodo HTTP. Possono essere usati per modificare parametri o risposte sugli endpoint.
  • I trasformatori di schema si applicano a ogni schema del documento. Questi possono essere usati per modificare lo schema dei corpi di richiesta o di risposta o per qualsiasi schema annidato.

I trasformatori possono essere registrati nel documento chiamando il AddDocumentTransformer metodo sull'oggetto OpenApiOptions . Il frammento di codice seguente illustra diversi modi per registrare i trasformatori nel documento:

  • Registrare un trasformatore di documento usando un delegato.
  • Registrare un trasformatore di documenti utilizzando un'istanza di IOpenApiDocumentTransformer.
  • Registrare un trasformatore di documento usando un'attivazione IOpenApiDocumentTransformerdell'inserimento delle dipendenze.
  • Registrare un trasformatore di operazioni attraverso un delegato.
  • Registrare un trasformatore di operazione usando un'istanza di IOpenApiOperationTransformer.
  • Registrare un trasformatore di operazioni utilizzando un'iniezione di dipendenze attivata IOpenApiOperationTransformer.
  • Registrare un trasformatore di schema usando un delegato.
  • Registrare un trasformatore di schema usando un'istanza di IOpenApiSchemaTransformer.
  • Registrare un trasformatore di schema utilizzando l'inserimento delle dipendenze attivato da DI.
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();

Ordine di esecuzione per trasformatori

I trasformatori vengono eseguiti nel seguente ordine:

  • I trasformatori di schema vengono eseguiti quando uno schema viene registrato nel documento. Vengono eseguiti nell'ordine in cui sono aggiunti. Tutti gli schemi vengono aggiunti al documento prima che si verifichi un'elaborazione dell'operazione, quindi i trasformatori dello schema vengono eseguiti prima dei trasformatori di operazione.
  • I trasformatori delle operazioni si attivano quando un'operazione viene aggiunta al documento. Vengono eseguiti nell'ordine in cui sono aggiunti. Tutte le operazioni vengono aggiunte al documento prima dell'esecuzione di eventuali trasformatori di documento.
  • I trasformatori di documento vengono eseguiti quando viene generato il documento. Questo è il passaggio finale sul documento e tutte le operazioni e gli schemi vengono aggiunti da questo punto.
  • Quando un'app è configurata per generare più documenti OpenAPI, i trasformatori vengono eseguiti in modo indipendente per ogni documento.

Ad esempio, nel frammento di codice seguente:

  • SchemaTransformer2 viene eseguito e ha accesso alle modifiche apportate da SchemaTransformer1.
  • Sia OperationTransformer1 che OperationTransformer2 hanno accesso alle modifiche apportate da entrambi i trasformatori di schema per i tipi coinvolti nell'operazione che vengono chiamati per elaborare.
  • OperationTransformer2 viene eseguito dopo OperationTransformer1, in modo che abbia accesso alle modifiche apportate da OperationTransformer1.
  • Sia DocumentTransformer1 che DocumentTransformer2 vengono eseguiti dopo che tutte le operazioni e gli schemi sono stati aggiunti al documento, quindi hanno accesso a tutte le modifiche apportate dall'operazione e dai trasformatori di schema.
  • DocumentTransformer2 viene eseguito dopo DocumentTransformer1, in modo che abbia accesso alle modifiche apportate da 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();

Usare trasformatori di documento

I trasformatori di documento hanno accesso a un oggetto contesto che include:

I trasformatori di documento possono anche modificare il documento OpenAPI generato. L'esempio seguente illustra un trasformatore di documento che aggiunge alcune informazioni sull'API al documento 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();

I trasformatori di documenti attivati dal servizio possono usare istanze da DI per modificare l'app. L'esempio seguente illustra un trasformatore di documento che usa il IAuthenticationSchemeProvider servizio dal livello di autenticazione. Controlla se gli schemi correlati al bearer JWT sono registrati nell'app e li aggiunge al livello principale del documento 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;
        }
    }
}

I trasformatori di documento sono univoci per l'istanza del documento a cui sono associati. Nell'esempio seguente un trasformatore:

  • Registra i requisiti correlati all'autenticazione nel internal documento.
  • Lascia invariato il public 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("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)] = []
                });
            }
        }
    }
}

Usare trasformatori operativi

Le operazioni sono combinazioni univoche di percorsi e metodi HTTP in un documento OpenAPI. I trasformatori operativi sono utili quando si sta modificando:

  • Dovrebbe essere eseguito ad ogni endpoint in un'app o
  • Applicato in modo condizionale a determinati percorsi.

I trasformatori di operazione hanno accesso a un oggetto contesto che contiene:

  • Nome del documento a cui appartiene l'operazione.
  • Classe ApiDescription associata all'operazione.
  • Oggetto IServiceProvider utilizzato nella generazione di documenti.

Ad esempio, il trasformatore di operazione seguente aggiunge 500 come codice di stato della risposta supportato da tutte le operazioni nel 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();

I trasformatori di operazione possono anche essere aggiunti a un endpoint specifico con l'API AddOpenApiOperationTransformer , invece di tutti gli endpoint in un documento. Ciò può essere utile per modificare dati OpenAPI specifici per un endpoint specifico, ad esempio l'aggiunta di uno schema di sicurezza, la descrizione della risposta o altre proprietà dell'operazione OpenAPI. Nell'esempio seguente viene illustrata l'aggiunta di un trasformatore di operazione a un endpoint deprecato, che contrassegna l'endpoint come deprecato nel 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();

Applicazione condizionale dei requisiti di sicurezza

In alcuni scenari, gli sviluppatori possono voler applicare requisiti di sicurezza a tutti gli endpoint, ad eccezione di quelli contrassegnati in modo esplicito con l'attributo AllowAnonymous .

Usare un trasformatore delle operazioni, che ha accesso ai metadati dell'endpoint tramite l'oggetto associato ApiDescription.

L'esempio seguente illustra come ignorare l'aggiunta di un requisito di sicurezza per gli endpoint a cui è AllowAnonymousAttribute stato applicato:

internal sealed class AuthOperationTransformer : IOpenApiOperationTransformer
{
    public Task TransformAsync(
        OpenApiOperation operation,
        OpenApiOperationTransformerContext context,
        CancellationToken cancellationToken)
    {
        var hasAllowAnonymous = context.Description.ActionDescriptor.EndpointMetadata
            .OfType<AllowAnonymousAttribute>()
            .Any();

        if (hasAllowAnonymous)
        {
            return Task.CompletedTask;
        }

        operation.Security ??= new List<OpenApiSecurityRequirement>();

        operation.Security.Add(new OpenApiSecurityRequirement
        {
            [new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Id = "Bearer",
                    Type = ReferenceType.SecurityScheme
                }
            }] = Array.Empty<string>()
        });

        return Task.CompletedTask;
    }
}

Usare questo approccio anziché i trasformatori di documento quando è necessaria la logica condizionale basata sui metadati dell'endpoint. Questo trasformatore aggiunge i requisiti di sicurezza per ogni operazione e presuppone che lo schema di sicurezza sia già registrato a livello di documento. Per un esempio di registrazione dello schema di sicurezza Bearer, vedere la sezione Uso dei trasformatori di documenti.

Usare trasformatori di schema

Gli schemi sono i modelli di dati usati nei corpi di richiesta e risposta in un documento OpenAPI. I trasformatori di schema sono utili quando una modifica:

  • Deve essere eseguito su ogni schema nel documento o
  • Applicato in modo condizionale a determinati schemi.

I trasformatori di schema hanno accesso a un oggetto contesto che contiene:

  • Nome del documento a cui appartiene lo schema.
  • Informazioni sul tipo JSON associate allo schema di destinazione.
  • Oggetto IServiceProvider utilizzato nella generazione di documenti.

Ad esempio, il trasformatore di schema seguente imposta il valore format dei tipi decimali su decimal anziché 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; }
}

Supporto per la generazione di OpenApiSchemas nei trasformatori

Gli sviluppatori possono generare uno schema per un tipo C# usando la stessa logica di ASP.NET generazione di documenti OpenAPI core e aggiungerlo al documento OpenAPI. È quindi possibile fare riferimento allo schema da un'altra posizione nel documento OpenAPI. Questa funzionalità è disponibile a partire da .NET 10.

Il contesto passato a documenti, operazioni e trasformatori di schema include un nuovo GetOrCreateSchemaAsync metodo che può essere usato per generare uno schema per un tipo. Questo metodo include anche un parametro facoltativo ApiParameterDescription per specificare metadati aggiuntivi per lo schema generato.

Per supportare l'aggiunta dello schema al documento OpenAPI, è stata aggiunta una Document proprietà ai contesti Operation e Schema Transformer. Ciò consente a qualsiasi trasformatore di aggiungere uno schema al documento OpenAPI usando il metodo del AddComponent documento.

Esempio

Per utilizzare questa funzionalità in un documento, un'operazione o un trasformatore di schema, crea lo schema utilizzando il metodo GetOrCreateSchemaAsync fornito nel contesto e aggiungilo al documento OpenAPI usando il metodo AddComponent.

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

Personalizzare il riutilizzo dello schema

Dopo l'applicazione di tutti i trasformatori, il framework esegue un passaggio sul documento per trasferire determinati schemi alla sezione components.schemas, sostituendoli con i riferimenti $ref allo schema trasferito. Ciò riduce le dimensioni del documento e semplifica la lettura.

I dettagli di questa elaborazione sono complicati e potrebbero cambiare nelle versioni future di .NET, ma in generale:

  • Gli schemi per i tipi classe/record/struct vengono sostituiti da uno schema in $ref a uno in components.schemas se appaiono più di una volta nel documento.
  • Gli schemi per i tipi primitivi e le raccolte standard vengono lasciati in linea.
  • Gli schemi per i tipi enum vengono sempre sostituiti con un $ref per uno schema in components.schemas.

In genere il nome dello schema in components.schemas è il nome del tipo classe/record/struct, ma in alcune circostanze è necessario usare un nome diverso.

ASP.NET Core consente di personalizzare quali schemi sono sostituiti da un $ref con uno schema in components.schemas usando la proprietà CreateSchemaReferenceId di OpenApiOptions. Questa proprietà è un delegato che prende un oggetto JsonTypeInfo e restituisce il nome dello schema in components.schemas da utilizzare per quel tipo. Il framework fornisce un'implementazione predefinita di questo delegato, CreateDefaultSchemaReferenceId che usa il nome del tipo, ma è possibile sostituirlo con la propria implementazione.

Come semplice esempio di questa personalizzazione, è possibile scegliere di usare sempre schemi di enumerazione inline. Questa operazione viene eseguita impostando CreateSchemaReferenceId su un delegato che restituisce null per i tipi enumerativi e altrimenti restituisce il valore dall'implementazione predefinita. Il codice seguente illustra quanto segue:

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

Risorse aggiuntive

Trasformatori di documento OpenAPI

I trasformatori forniscono un'API per la modifica del documento OpenAPI con personalizzazioni definite dall'utente. I trasformatori sono utili per scenari come:

  • Aggiunta di parametri a tutte le operazioni in un documento.
  • Modifica delle descrizioni per parametri o operazioni.
  • Aggiunta di informazioni di livello superiore al documento OpenAPI.

I trasformatori rientrano in tre categorie:

  • I trasformatori di documento hanno accesso all'intero documento OpenAPI. Questi possono essere usati per apportare modifiche globali al documento.
  • I trasformatori di operazione si applicano a ogni singola operazione. Ogni singola operazione è una combinazione di percorso e metodo HTTP. Possono essere usati per modificare parametri o risposte sugli endpoint.
  • I trasformatori di schema si applicano a ogni schema del documento. Questi possono essere usati per modificare lo schema dei corpi di richiesta o di risposta o per qualsiasi schema annidato.

I trasformatori possono essere registrati nel documento chiamando il AddDocumentTransformer metodo sull'oggetto OpenApiOptions . Il frammento di codice seguente illustra diversi modi per registrare i trasformatori nel documento:

  • Registrare un trasformatore di documento usando un delegato.
  • Registrare un trasformatore di documenti utilizzando un'istanza di IOpenApiDocumentTransformer.
  • Registrare un trasformatore di documento usando un'attivazione IOpenApiDocumentTransformerdell'inserimento delle dipendenze.
  • Registrare un trasformatore di operazioni attraverso un delegato.
  • Registrare un trasformatore di operazione usando un'istanza di IOpenApiOperationTransformer.
  • Registrare un trasformatore di operazioni utilizzando un'iniezione di dipendenze attivata IOpenApiOperationTransformer.
  • Registrare un trasformatore di schema usando un delegato.
  • Registrare un trasformatore di schema usando un'istanza di IOpenApiSchemaTransformer.
  • Registrare un trasformatore di schema utilizzando l'inserimento delle dipendenze attivato da DI.
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();

Ordine di esecuzione per trasformatori

I trasformatori vengono eseguiti come segue:

  • I trasformatori di schema vengono eseguiti quando uno schema viene registrato nel documento. I trasformatori di schema vengono eseguiti nell'ordine in cui sono stati aggiunti. Tutti gli schemi vengono aggiunti al documento prima che si verifichi un'elaborazione dell'operazione, quindi tutti i trasformatori di schema vengono eseguiti prima di qualsiasi trasformatore di operazione.
  • I trasformatori di operazione vengono eseguiti quando viene aggiunta un'operazione al documento. I trasformatori operativi vengono eseguiti nell'ordine in cui sono stati aggiunti. Tutte le operazioni vengono aggiunte al documento prima dell'esecuzione di eventuali trasformatori di documento.
  • I trasformatori di documento vengono eseguiti quando viene generato il documento. Si tratta del passaggio finale del documento e tutte le operazioni e gli schemi sono stati aggiunti da questo punto.
  • Quando un'app è configurata per generare più documenti OpenAPI, i trasformatori vengono eseguiti per ogni documento in modo indipendente.

Ad esempio, nel frammento di codice seguente:

  • SchemaTransformer2 viene eseguito e ha accesso alle modifiche apportate da SchemaTransformer1.
  • Sia OperationTransformer1 che OperationTransformer2 hanno accesso alle modifiche apportate da entrambi i trasformatori di schema per i tipi coinvolti nell'operazione che vengono chiamati per elaborare.
  • OperationTransformer2 viene eseguito dopo OperationTransformer1, in modo che abbia accesso alle modifiche apportate da OperationTransformer1.
  • Sia DocumentTransformer1 che DocumentTransformer2 vengono eseguiti dopo che tutte le operazioni e gli schemi sono stati aggiunti al documento, quindi hanno accesso a tutte le modifiche apportate dall'operazione e dai trasformatori di schema.
  • DocumentTransformer2 viene eseguito dopo DocumentTransformer1, in modo che abbia accesso alle modifiche apportate da 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();

Usare trasformatori di documento

I trasformatori di documento hanno accesso a un oggetto contesto che include:

I trasformatori di documento possono anche modificare il documento OpenAPI generato. L'esempio seguente illustra un trasformatore di documento che aggiunge alcune informazioni sull'API al documento 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();

I trasformatori di documenti attivati dal servizio possono usare istanze da DI per modificare l'app. L'esempio seguente illustra un trasformatore di documento che usa il IAuthenticationSchemeProvider servizio dal livello di autenticazione. Controlla se gli schemi correlati al bearer JWT sono registrati nell'app e li aggiunge al livello principale del documento 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;
        }
    }
}

I trasformatori di documento sono univoci per l'istanza del documento a cui sono associati. Nell'esempio seguente un trasformatore:

  • Registra i requisiti correlati all'autenticazione nel internal documento.
  • Lascia invariato il public 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("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>()
                });
            }
        }
    }
}

Usare trasformatori operativi

Le operazioni sono combinazioni univoche di percorsi e metodi HTTP in un documento OpenAPI. I trasformatori operativi sono utili quando si sta modificando:

  • Dovrebbe essere eseguito ad ogni endpoint in un'app o
  • Applicato in modo condizionale a determinati percorsi.

I trasformatori di operazione hanno accesso a un oggetto contesto che contiene:

  • Nome del documento a cui appartiene l'operazione.
  • Classe ApiDescription associata all'operazione.
  • Oggetto IServiceProvider utilizzato nella generazione di documenti.

Ad esempio, il trasformatore di operazione seguente aggiunge 500 come codice di stato della risposta supportato da tutte le operazioni nel 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();

Usare trasformatori di schema

Gli schemi sono i modelli di dati usati nei corpi di richiesta e risposta in un documento OpenAPI. I trasformatori di schema sono utili quando una modifica:

  • Deve essere eseguito su ogni schema nel documento o
  • Applicato in modo condizionale a determinati schemi.

I trasformatori di schema hanno accesso a un oggetto contesto che contiene:

  • Nome del documento a cui appartiene lo schema.
  • Informazioni sul tipo JSON associate allo schema di destinazione.
  • Oggetto IServiceProvider utilizzato nella generazione di documenti.

Ad esempio, il trasformatore di schema seguente imposta il valore format dei tipi decimali su decimal anziché 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; }
}

Personalizzare il riutilizzo dello schema

Dopo l'applicazione di tutti i trasformatori, il framework esegue un passaggio sul documento per trasferire determinati schemi alla sezione components.schemas, sostituendoli con i riferimenti $ref allo schema trasferito. Ciò riduce le dimensioni del documento e semplifica la lettura.

I dettagli di questa elaborazione sono complicati e potrebbero cambiare nelle versioni future di .NET, ma in generale:

  • Gli schemi per i tipi classe/record/struct vengono sostituiti da uno schema in $ref a uno in components.schemas se appaiono più di una volta nel documento.
  • Gli schemi per i tipi primitivi e le raccolte standard vengono lasciati in linea.
  • Gli schemi per i tipi enum vengono sempre sostituiti con un $ref per uno schema in components.schemas.

In genere il nome dello schema in components.schemas è il nome del tipo classe/record/struct, ma in alcune circostanze è necessario usare un nome diverso.

ASP.NET Core consente di personalizzare quali schemi sono sostituiti da un $ref con uno schema in components.schemas usando la proprietà CreateSchemaReferenceId di OpenApiOptions. Questa proprietà è un delegato che prende un oggetto JsonTypeInfo e restituisce il nome dello schema in components.schemas da utilizzare per quel tipo. Il framework fornisce un'implementazione predefinita di questo delegato, CreateDefaultSchemaReferenceId che usa il nome del tipo, ma è possibile sostituirlo con la propria implementazione.

Come semplice esempio di questa personalizzazione, è possibile scegliere di usare sempre schemi di enumerazione inline. Questa operazione viene eseguita impostando CreateSchemaReferenceId su un delegato che restituisce null per i tipi enumerativi e altrimenti restituisce il valore dall'implementazione predefinita. Il codice seguente illustra come eseguire questa operazione:

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

Risorse aggiuntive