Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
OpenAPI document transformers
Transformers provide an API for modifying the OpenAPI document with user-defined customizations. Transformers are useful for scenarios like:
- Adding parameters to all operations in a document.
- Modifying descriptions for parameters or operations.
- Adding top-level information to the OpenAPI document.
Transformers fall into three categories:
- Document transformers have access to the entire OpenAPI document. These can be used to make global modifications to the document.
- Operation transformers apply to each individual operation. Each individual operation is a combination of path and HTTP method. These can be used to modify parameters or responses on endpoints.
- Schema transformers apply to each schema in the document. These can be used to modify the schema of request or response bodies, or any nested schemas.
Transformers can be registered onto the document by calling the AddDocumentTransformer method on the OpenApiOptions object. The following snippet shows different ways to register transformers onto the document:
- Register a document transformer using a delegate.
- Register a document transformer using an instance of IOpenApiDocumentTransformer.
- Register a document transformer using a DI-activated IOpenApiDocumentTransformer.
- Register an operation transformer using a delegate.
- Register an operation transformer using an instance of IOpenApiOperationTransformer.
- Register an operation transformer using a DI-activated IOpenApiOperationTransformer.
- Register a schema transformer using a delegate.
- Register a schema transformer using an instance of IOpenApiSchemaTransformer.
- Register a schema transformer using a DI-activated IOpenApiSchemaTransformer.
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();
Execution order for transformers
Transformers execute in the following order:
- Schema transformers execute when a schema is registered to the document. They execute in the order they're added. All schemas are added to the document before any operation processing occurs, so schema transformers execute before operation transformers.
- Operation transformers execute when an operation is added to the document. They execute in the order they're added. All operations are added to the document before any document transformers execute.
- Document transformers execute when the document is generated. This is the final pass over the document, and all operations and schemas are added by this point.
- When an app is configured to generate multiple OpenAPI documents, transformers execute for each document independently.
For example, in the following snippet:
SchemaTransformer2
is executed and has access to the modifications made bySchemaTransformer1
.- Both
OperationTransformer1
andOperationTransformer2
have access to the modifications made by both schema transformers for the types involved in the operation they're called to process. OperationTransformer2
is executed afterOperationTransformer1
, so it has access to the modifications made byOperationTransformer1
.- Both
DocumentTransformer1
andDocumentTransformer2
are executed after all operations and schemas have been added to the document, so they have access to all modifications made by the operation and schema transformers. DocumentTransformer2
is executed afterDocumentTransformer1
, so it has access to the modifications made byDocumentTransformer1
.
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();
Use document transformers
Document transformers have access to a context object that includes:
- The name of the document being modified.
- The ApiDescriptionGroups associated with that document.
- The IServiceProvider used in document generation.
Document transformers can also mutate the OpenAPI document that is generated. The following example demonstrates a document transformer that adds some information about the API to the OpenAPI document.
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();
Service-activated document transformers can utilize instances from DI to modify the app. The following sample demonstrates a document transformer that uses the IAuthenticationSchemeProvider service from the authentication layer. It checks if any JWT bearer-related schemes are registered in the app and adds them to the OpenAPI document's top level:
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;
}
}
}
Document transformers are unique to the document instance they're associated with. In the following example, a transformer:
- Registers authentication-related requirements to the
internal
document. - Leaves the
public
document unmodified.
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>()
});
}
}
}
}
Use operation transformers
Operations are unique combinations of HTTP paths and methods in an OpenAPI document. Operation transformers are helpful when a modification:
- Should be made to each endpoint in an app, or
- Conditionally applied to certain routes.
Operation transformers have access to a context object which contains:
- The name of the document the operation belongs to.
- The ApiDescription associated with the operation.
- The IServiceProvider used in document generation.
For example, the following operation transformer adds 500
as a response status code supported by all operations in the document.
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();
Use schema transformers
Schemas are the data models that are used in request and response bodies in an OpenAPI document. Schema transformers are useful when a modification:
- Should be made to each schema in the document, or
- Conditionally applied to certain schemas.
Schema transformers have access to a context object which contains:
- The name of the document the schema belongs to.
- The JSON type information associated with the target schema.
- The IServiceProvider used in document generation.
For example, the following schema transformer sets the format
of decimal types to decimal
instead of 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; }
}
Support for generating OpenApiSchemas in transformers
Developers can generate a schema for a C# type using the same logic as ASP.NET Core OpenAPI document generation and add it to the OpenAPI document. The schema can then be referenced from elsewhere in the OpenAPI document. This capability is available starting with .NET 10.
The context passed to document, operation, and schema transformers includes a new GetOrCreateSchemaAsync
method that can be used to generate a schema for a type.
This method also has an optional ApiParameterDescription
parameter to specify additional metadata for the generated schema.
To support adding the schema to the OpenAPI document, a Document
property has been added to the Operation and Schema transformer contexts. This allows any transformer to add a schema to the OpenAPI document using the document's AddComponent
method.
Example
To use this feature in a document, operation, or schema transformer, create the schema using the GetOrCreateSchemaAsync
method provided in the context and add it to the OpenAPI document using the document's AddComponent
method.
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)
}
}
};
});
});
Customize schema reuse
After all transformers have been applied, the framework makes a pass over the document to transfer certain schemas
to the components.schemas
section, replacing them with $ref
references to the transferred schema.
This reduces the size of the document and makes it easier to read.
The details of this processing are complicated and might change in future versions of .NET, but in general:
- Schemas for class/record/struct types are replaced with a
$ref
to a schema incomponents.schemas
if they appear more than once in the document. - Schemas for primitive types and standard collections are left inline.
- Schemas for enum types are always replaced with a
$ref
to a schema in components.schemas.
Typically the name of the schema in components.schemas
is the name of the class/record/struct type,
but in some circumstances a different name must be used.
ASP.NET Core lets you customize which schemas are replaced with a $ref
to a schema in components.schemas
using the CreateSchemaReferenceId property of OpenApiOptions.
This property is a delegate that takes a JsonTypeInfo object and returns the name of the schema
in components.schemas
that should be used for that type.
The framework provides a default implementation of this delegate, CreateDefaultSchemaReferenceId
that uses the name of the type, but you can replace it with your own implementation.
As a simple example of this customization, you might choose to always inline enum schemas. This is done by setting CreateSchemaReferenceId to a delegate that returns null for enum types, and otherwise returns the value from the default implementation. The following code demonstrates this:
builder.Services.AddOpenApi(options =>
{
// Always inline enum schemas
options.CreateSchemaReferenceId = (type) =>
type.Type.IsEnum ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type);
});
Additional resources
OpenAPI document transformers
Transformers provide an API for modifying the OpenAPI document with user-defined customizations. Transformers are useful for scenarios like:
- Adding parameters to all operations in a document.
- Modifying descriptions for parameters or operations.
- Adding top-level information to the OpenAPI document.
Transformers fall into three categories:
- Document transformers have access to the entire OpenAPI document. These can be used to make global modifications to the document.
- Operation transformers apply to each individual operation. Each individual operation is a combination of path and HTTP method. These can be used to modify parameters or responses on endpoints.
- Schema transformers apply to each schema in the document. These can be used to modify the schema of request or response bodies, or any nested schemas.
Transformers can be registered onto the document by calling the AddDocumentTransformer method on the OpenApiOptions object. The following snippet shows different ways to register transformers onto the document:
- Register a document transformer using a delegate.
- Register a document transformer using an instance of IOpenApiDocumentTransformer.
- Register a document transformer using a DI-activated IOpenApiDocumentTransformer.
- Register an operation transformer using a delegate.
- Register an operation transformer using an instance of IOpenApiOperationTransformer.
- Register an operation transformer using a DI-activated IOpenApiOperationTransformer.
- Register a schema transformer using a delegate.
- Register a schema transformer using an instance of IOpenApiSchemaTransformer.
- Register a schema transformer using a DI-activated IOpenApiSchemaTransformer.
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();
Execution order for transformers
Transformers are executed as follows:
- Schema transformers are executed when a schema is registered to the document. Schema transformers are executed in the order in which they were added. All schemas are added to the document before any operation processing occurs, so all schema transformers are executed before any operation transformers.
- Operation transformers are executed when an operation is added to the document. Operation transformers are executed in the order in which they were added. All operations are added to the document before any document transformers are executed.
- Document transformers are executed when the document is generated. This is the final pass over the document, and all operations and schemas have been add by this point.
- When an app is configured to generate multiple OpenAPI documents, transformers are executed for each document independently.
For example, in the following snippet:
SchemaTransformer2
is executed and has access to the modifications made bySchemaTransformer1
.- Both
OperationTransformer1
andOperationTransformer2
have access to the modifications made by both schema transformers for the types involved in the operation they are called to process. OperationTransformer2
is executed afterOperationTransformer1
, so it has access to the modifications made byOperationTransformer1
.- Both
DocumentTransformer1
andDocumentTransformer2
are executed after all operations and schemas have been added to the document, so they have access to all modifications made by the operation and schema transformers. DocumentTransformer2
is executed afterDocumentTransformer1
, so it has access to the modifications made byDocumentTransformer1
.
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();
Use document transformers
Document transformers have access to a context object that includes:
- The name of the document being modified.
- The ApiDescriptionGroups associated with that document.
- The IServiceProvider used in document generation.
Document transformers can also mutate the OpenAPI document that is generated. The following example demonstrates a document transformer that adds some information about the API to the OpenAPI document.
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();
Service-activated document transformers can utilize instances from DI to modify the app. The following sample demonstrates a document transformer that uses the IAuthenticationSchemeProvider service from the authentication layer. It checks if any JWT bearer-related schemes are registered in the app and adds them to the OpenAPI document's top level:
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;
}
}
}
Document transformers are unique to the document instance they're associated with. In the following example, a transformer:
- Registers authentication-related requirements to the
internal
document. - Leaves the
public
document unmodified.
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>()
});
}
}
}
}
Use operation transformers
Operations are unique combinations of HTTP paths and methods in an OpenAPI document. Operation transformers are helpful when a modification:
- Should be made to each endpoint in an app, or
- Conditionally applied to certain routes.
Operation transformers have access to a context object which contains:
- The name of the document the operation belongs to.
- The ApiDescription associated with the operation.
- The IServiceProvider used in document generation.
For example, the following operation transformer adds 500
as a response status code supported by all operations in the document.
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();
Use schema transformers
Schemas are the data models that are used in request and response bodies in an OpenAPI document. Schema transformers are useful when a modification:
- Should be made to each schema in the document, or
- Conditionally applied to certain schemas.
Schema transformers have access to a context object which contains:
- The name of the document the schema belongs to.
- The JSON type information associated with the target schema.
- The IServiceProvider used in document generation.
For example, the following schema transformer sets the format
of decimal types to decimal
instead of 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; }
}
Customize schema reuse
After all transformers have been applied, the framework makes a pass over the document to transfer certain schemas
to the components.schemas
section, replacing them with $ref
references to the transferred schema.
This reduces the size of the document and makes it easier to read.
The details of this processing are complicated and might change in future versions of .NET, but in general:
- Schemas for class/record/struct types are replaced with a
$ref
to a schema incomponents.schemas
if they appear more than once in the document. - Schemas for primitive types and standard collections are left inline.
- Schemas for enum types are always replaced with a
$ref
to a schema in components.schemas.
Typically the name of the schema in components.schemas
is the name of the class/record/struct type,
but in some circumstances a different name must be used.
ASP.NET Core lets you customize which schemas are replaced with a $ref
to a schema in components.schemas
using the CreateSchemaReferenceId property of OpenApiOptions.
This property is a delegate that takes a JsonTypeInfo object and returns the name of the schema
in components.schemas
that should be used for that type.
The framework provides a default implementation of this delegate, CreateDefaultSchemaReferenceId
that uses the name of the type, but you can replace it with your own implementation.
As a simple example of this customization, you might choose to always inline enum schemas. This is done by setting CreateSchemaReferenceId to a delegate that returns null for enum types, and otherwise returns the value from the default implementation. The following code shows how to do this:
builder.Services.AddOpenApi(options =>
{
// Always inline enum schemas
options.CreateSchemaReferenceId = (type) =>
type.Type.IsEnum ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type);
});
Additional resources
ASP.NET Core