ASP.NET Core in .NET 9 streamlines the process of creating OpenAPI documents for your API endpoints with new built-in support for OpenAPI document generation. This new feature is designed to simplify the development workflow and improve the integration of OpenAPI definitions in your ASP.NET applications. And OpenAPI’s broad adoption has fostered a rich ecosystem of tools and services that can help you build, test, and document your APIs more effectively. Some examples are Swagger UI, the Kiota client library generator, and Redoc, but there are many, many more.
Why OpenAPI?
OpenAPI is a powerful tool for defining and documenting HTTP APIs. It provides a standard way to describe your API’s endpoints, request and response formats, authentication schemes, and other essential details. This standardization makes it easier for developers to understand and interact with APIs, leading to better collaboration and more robust applications. And
In addition, many large language models (LLMs) have been trained on OpenAPI documents, enabling them to generate code, test cases, and other artifacts automatically. By producing OpenAPI documents for your APIs, you can take advantage of these LLMs to accelerate your development process.
What’s New in .NET 9?
With .NET 9, we are introducing built-in support for OpenAPI document generation that provides a more integrated and seamless experience for .NET developers. This feature can be used in both Minimal APIs and controller-based applications. Here are some of the key highlights:
- Support for generating OpenAPI documents at run time and accessing them via an endpoint on the application, or generating them at build time.
- Attributes and extension methods for adding metadata to API methods and data.
- Support for “transformer” APIs that allow modifying the generated document in a variety of ways.
- Support for generating multiple OpenAPI documents from a single app.
- Takes advantage of JSON schema support provided by System.Text.Json.
- Is compatible with native AoT when used in conjunction with Minimal APIs.
How to get started
Getting started with the new OpenAPI document generation feature in .NET 9 is straightforward. Here’s a quick guide to help you begin.
Update to .NET 9
Ensure that your project is using .NET 9, which was released earlier this month. You can download the latest version from the official .NET website.
If you are adding OpenAPI support to an existing project, you will need to update your project to target .NET 9. There’s a detailed migration guide for this in the ASP.NET Core docs.
Enable OpenAPI support
If you are starting a new project, OpenAPI support is already built in to the .NET 9 webapi
template.
To enable OpenAPI document in an existing project, you just need to add the Microsoft.AspNetCore.OpenApi package and add a few lines of code to your main application file.
You can add the package with the dotnet add package
command:
dotnet add package Microsoft.AspNetCore.OpenApi
After that, in your Program.cs
file, you need to add the OpenAPI services to the WebApplicationBuilder:
builder.Services.AddOpenApi();
There are various configuration options available for the OpenAPI feature, such as setting the document title, version, and other metadata. You can find more information on these options in the ASP.NET Core docs.
Then add the endpoint to your app to serve the OpenAPI document with the MapOpenApi
extension method, like this:
app.MapOpenApi();
Now you can run your application and access the generated OpenAPI document at the /openapi/v1.json
endpoint.
What you will see there is an OpenAPI document with paths, operations, and schemas based on the code for your application, but maybe not important details like descriptions and examples. To get these elements, you will need to add metadata as described in the next section.
Add OpenAPI metadata
Descriptions, tags, examples, and other metadata can be added to your API methods and data to give meaning to the generated OpenAPI document. This metadata can be added using attributes or extension methods.
You can add a summary and description for each endpoint in your application using the WithSummary
and WithDescription
extension methods:
app.MapGet("/hello", () => "Hello, World!")
.WithSummary("Get a greeting")
.WithDescription("This endpoint returns a friendly greeting.");
Endpoint summaries and descriptions are very important because they tell users (and LLMs) what things they can accomplish with your API.
You may also want to group related endpoints together in documentation, and this is usually done with tags.
You can add tags to your endpoints using the WithTag
extension method:
app.MapGet("/hello", () => "Hello, World!")
.WithTag("Greetings");
When an endpoint has parameters, it is important to include a description on each parameter to explain its meaning
and how it is used by the endpoint. Use the [Description]
attribute to add a description to a parameter:
app.MapGet("/hello/{name}",
(
[Description("The name of the person to greet.")] string name
) => $"Hello, {name}!")
.WithSummary("Get a personalized greeting")
.WithDescription("This endpoint returns a personalized greeting.")
.WithTag("Greetings");
You can also use the [Description]
attribute to add descriptions to properties in your data models:
public record Person
{
[Description("The person's name.")]
public string Name { get; init; }
[Description("The person's age.")]
public int Age { get; init; }
}
There are many other metadata attributes for describing parameters and properties, including [MaxLength]
, [Range]
, [RegularExpression]
, and [DefaultValue]
. Note that in controller-based apps, these attributes trigger validations
that are performed during model binding, but in Minimal APIs, they are used only for documentation.
See the Include OpenAPI metadata topic in the docs to learn more about adding metadata to your API methods and data.
Customize your documents
ASP.NET also provides a way to customize the generated OpenAPI document using “transformers”,
which can operate on the entire document, on operations, or on schemas.
Transformers are classes that implement the IOpenApiDocumentTransformer
, IOpenApiOperationTransformer
, or IOpenApiSchemaTransformer
interfaces. Each of these interfaces has a single async method that receives the document, operation, or schema to be transformed along with a context object that provides additional information.
The OpenAPI document, operation, or schema passed to a transformer is a strongly-typed object
using the types from the Microsoft.OpenApi.Models namespace.
The method performs the transformation “in place” by modifying the object it receives.
Transformers are added by the configureOptions
delegate parameter of the AddOpenApi
call,
and can specified as an instance of a class, as a DI-activated class, or as a delegate method.
builder.Services.AddOpenApi(options =>
{
// document transformer added as an instance of a class
options.AddDocumentTransformer(new MyDocumentTransformer());
// operation transformer added as a DI-activated class
options.AddOperationTransformer<MyOperationTransformer>();
// schema transformer added as a delegate method
options.AddSchemaTransformer((schema, context, cancellationToken)
=> Task.CompletedTask);
});
One application of document transformers is to modify portions of the OpenAPI document outside the paths
and components.schemas
sections. For example, you could add a contact
in the info
element of the document like this:
builder.Services.AddOpenApi(options =>
{
options.AddDocumentTransformer((document, context, cancellationToken) =>
{
document.Info.Contact = new OpenApiContact
{
Name = "Contoso Support",
Email = "support@contoso.com"
};
return Task.CompletedTask;
}
});
Operation transformers can be used to modify individual operations in the document. An operation transformer is invoked
on every operation in the app, and it can choose to modify the operation or not. For example, you could add a security
requirement to all operations that require authorization like this:
options.AddOperationTransformer((operation, context, cancellationToken) =>
{
if (context.Description.ActionDescriptor.EndpointMetadata.OfType<IAuthorizeData>().Any())
{
operation.Security = [new() { ["Bearer"] = [] }];
}
return Task.CompletedTask;
});
Schema transformers can be used to modify the schemas for the application. Schemas describe the request or response bodies of operations. Complex properties within a request or response body may have their own schemas. Schema transformers can be used to modify any or all of these schemas.
It is important to know that all transformers, including schema transformers, are invoked before schemas are converted to “$ref” references — a process discussed in the next section.
The following example shows a simple schema transformer that sets the format
field to decimal
for any schema
representing a C# decmial
value.
options.AddSchemaTransformer((schema, context, cancellationToken) =>
{
if (context.JsonTypeInfo.Type == typeof(decimal))
{
// default schema for decimal is just type: number. Add format: decimal
schema.Format = "decimal";
}
return Task.CompletedTask;
});
Customize schema reuse
After all transformers have been applied, the framework makes a pass over the document transferring 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 a bit 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 tocomponents.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, OpenApiOptions.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 always returns null
for enum types,
and otherwise returns 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);
});
Generating OpenAPI documents at build time
A feature that I think many .NET developers will find appealing is the option to generate the OpenAPI document at build time. Generating the OpenAPI document as part of the build process makes it much easier to integrate with tools in your local development workflow or CI pipeline. For example, you can run a linter on the generated document to ensure it meets your organization’s standards, or you can use the document to generate client code or tests.
Generating the OpenAPI document at build time is simple. Just add the Microsoft.Extensions.ApiDescription.Server
package to your project. By default, the OpenAPI document is generated into the obj
directory of your project, but you can customize the location of the generated document with the OpenApiDocumentsDirectory
property. For example, to generate the document into the root directory of your project, add the following to your project file:
<PropertyGroup>
<OpenApiDocumentsDirectory>./</OpenApiDocumentsDirectory>
</PropertyGroup>
Note that build-time OpenAPI document generation works by launching the application’s entrypoint with an inert server implementation. This allows the framework to incorporate metadata that is only available at runtime, but could require some changes in your application to work properly in certain build scenarios.
See the Generating OpenAPI at Build Time topic in the documentation for more information.
Conclusion
The new OpenAPI document generation feature in .NET 9 provides developers with a new path to create and maintain API documentation for their ASP.NET apps. By integrating this functionality directly into ASP.NET Core, developers can now generate OpenAPI documents either at build time or run time, customize them as needed, and ensure they stay in sync with their code. And in Minimal API apps, the feature is fully compatible with native AoT compilation.
We’d love to hear your feedback on this new feature. Please try it out and let us know what you think.
Happy coding!
Hi everyone. This might be a "newbie" question in regards to why its happening but I upgraded a .NET 6 application to .NET 9 using the upgrade assistant; then attempted to add OpenAPI to it. Prior to adding the package I was building and running successfully. Now after adding the package I get:
"There was no runtime pack for Microsoft.AspNetCore.App available for the specified RuntimeIdentifier 'iossimulator-x64'."
and
"There was no runtime pack...
I’m not sure what’s going on there but I recommend that you open an issue in the aspnetcore repo so we can investigate.
https://github.com/dotnet/aspnetcore/issues
What about API versioning?
Why endpoint summary and description are added via extension methods, while descriptions of parameters and entity’s properties is done via attributes?
Seems a very inconsistent approach.
I was expecting something like:
You can use attributes to add summary, description, and operationId to operations in Minimal APIs, but the syntax is non-obvious. You have to put the attributes on the delegate method (the second parameter to MapGet), like this:
<code>
The extension methods are a little cleaner and more discoverable because of intellisense, but use whichever mechanism you prefer.
is a method call. You cannot add attributes to method calls. Attributes are metadata applied to information baked at compile time such as types and type members. A function call doesn't occur until runtime so attributes cannot be applied. The syntax you gave is not supported.
What you'd have to do instead is define a type that represents the call, such as a controller method or endpoint class. Then you can add attributes to...
It looks great, but unfortunately we will need to wait a year for .net 10 before we can adopt it because it doesn’t support extracting information from XML comments yet. I know it’s in the works, and I really hope you can get that into a version before v10.