{"id":54729,"date":"2024-11-25T12:00:00","date_gmt":"2024-11-25T20:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=54729"},"modified":"2024-11-25T12:00:00","modified_gmt":"2024-11-25T20:00:00","slug":"dotnet9-openapi","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/dotnet9-openapi\/","title":{"rendered":"OpenAPI document generation in .NET 9"},"content":{"rendered":"<p>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&#8217;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 <a href=\"https:\/\/swagger.io\/tools\/swagger-ui\/\">Swagger UI<\/a>, the <a href=\"https:\/\/learn.microsoft.com\/openapi\/kiota\/\">Kiota client library generator<\/a>, and <a href=\"https:\/\/redoc.ly\/\">Redoc<\/a>, but there are many, many more.<\/p>\n<h2>Why OpenAPI?<\/h2>\n<p><a href=\"https:\/\/www.openapis.org\/what-is-openapi\">OpenAPI<\/a> is a powerful tool for defining and documenting HTTP APIs. It provides a standard way to describe your API&#8217;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<\/p>\n<p>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.<\/p>\n<h2>What&#8217;s New in .NET 9?<\/h2>\n<p>With .NET 9, we are introducing built-in support for OpenAPI document generation that provides a more integrated and seamless experience for .NET developers.\nThis feature can be used in both Minimal APIs and controller-based applications.\nHere are some of the key highlights:<\/p>\n<ul>\n<li>Support for generating OpenAPI documents at run time and accessing them via an endpoint on the application, or generating them at build time.<\/li>\n<li>Attributes and extension methods for adding metadata to API methods and data.<\/li>\n<li>Support for &#8220;transformer&#8221; APIs that allow modifying the generated document in a variety of ways.<\/li>\n<li>Support for generating multiple OpenAPI documents from a single app.<\/li>\n<li>Takes advantage of JSON schema support provided by <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/system.text.json\">System.Text.Json<\/a>.<\/li>\n<li>Is compatible with native AoT when used in conjunction with Minimal APIs.<\/li>\n<\/ul>\n<h2>How to get started<\/h2>\n<p>Getting started with the new OpenAPI document generation feature in .NET 9 is straightforward. Here&#8217;s a quick guide to help you begin.<\/p>\n<h3>Update to .NET 9<\/h3>\n<p>Ensure that your project is using .NET 9, which was released earlier this month. You can download the latest version from the <a href=\"https:\/\/get.dot.net\/9\">official .NET website<\/a>.<\/p>\n<p>If you are adding OpenAPI support to an existing project, you will need to update your project to target .NET 9.\nThere&#8217;s a detailed migration guide for this in the <a href=\"https:\/\/learn.microsoft.com\/aspnet\/core\/migration\/80-90?view=aspnetcore-9.0\">ASP.NET Core docs<\/a>.<\/p>\n<h3>Enable OpenAPI support<\/h3>\n<p>If you are starting a new project, OpenAPI support is already built in to the .NET 9 <code>webapi<\/code> template.<\/p>\n<p>To enable OpenAPI document in an existing project, you just need to add the <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNetCore.OpenApi\">Microsoft.AspNetCore.OpenApi<\/a> package and add a few lines of code to your main application file.<\/p>\n<p>You can add the package with the <code>dotnet add package<\/code> command:<\/p>\n<pre><code class=\"language-bash\">dotnet add package Microsoft.AspNetCore.OpenApi<\/code><\/pre>\n<p>After that, in your <code>Program.cs<\/code> file, you need to add the OpenAPI services to the WebApplicationBuilder:<\/p>\n<pre><code class=\"language-csharp\">builder.Services.AddOpenApi();<\/code><\/pre>\n<p>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 <a href=\"https:\/\/learn.microsoft.com\/aspnet\/core\/migration\/80-90?view=aspnetcore-9.0\">ASP.NET Core docs<\/a>.<\/p>\n<p>Then add the endpoint to your app to serve the OpenAPI document with the <code>MapOpenApi<\/code> extension method, like this:<\/p>\n<pre><code class=\"language-csharp\">app.MapOpenApi();<\/code><\/pre>\n<p>Now you can run your application and access the generated OpenAPI document at the <code>\/openapi\/v1.json<\/code> endpoint.<\/p>\n<p>What you will see there is an OpenAPI document with paths, operations, and schemas based on the code\nfor your application, but maybe not important details like descriptions and examples. To get these elements,\nyou will need to add metadata as described in the next section.<\/p>\n<h3>Add OpenAPI metadata<\/h3>\n<p>Descriptions, tags, examples, and other metadata can be added to your API methods and data to give meaning\nto the generated OpenAPI document. This metadata can be added using attributes or extension methods.<\/p>\n<p>You can add a summary and description for each endpoint in your application using the <code>WithSummary<\/code> and <code>WithDescription<\/code> extension methods:<\/p>\n<pre><code class=\"language-csharp\">app.MapGet(\"\/hello\", () =&gt; \"Hello, World!\")\n    .WithSummary(\"Get a greeting\")\n    .WithDescription(\"This endpoint returns a friendly greeting.\");<\/code><\/pre>\n<p>Endpoint summaries and descriptions are very important because they tell users (and LLMs) what things they can\naccomplish with your API.<\/p>\n<p>You may also want to group related endpoints together in documentation, and this is usually done with tags.\nYou can add tags to your endpoints using the <code>WithTag<\/code> extension method:<\/p>\n<pre><code class=\"language-csharp\">app.MapGet(\"\/hello\", () =&gt; \"Hello, World!\")\n    .WithTag(\"Greetings\");<\/code><\/pre>\n<p>When an endpoint has parameters, it is important to include a description on each parameter to explain its meaning\nand how it is used by the endpoint. Use the <code>[Description]<\/code> attribute to add a description to a parameter:<\/p>\n<pre><code class=\"language-csharp\">app.MapGet(\"\/hello\/{name}\",\n(\n    [Description(\"The name of the person to greet.\")] string name\n) =&gt; $\"Hello, {name}!\")\n    .WithSummary(\"Get a personalized greeting\")\n    .WithDescription(\"This endpoint returns a personalized greeting.\")\n    .WithTag(\"Greetings\");<\/code><\/pre>\n<p>You can also use the <code>[Description]<\/code> attribute to add descriptions to properties in your data models:<\/p>\n<pre><code class=\"language-csharp\">public record Person\n{\n    [Description(\"The person's name.\")]\n    public string Name { get; init; }\n\n    [Description(\"The person's age.\")]\n    public int Age { get; init; }\n}<\/code><\/pre>\n<p>There are many other metadata attributes for describing parameters and properties, including <code>[MaxLength]<\/code>, <code>[Range]<\/code>, <code>[RegularExpression]<\/code>, and <code>[DefaultValue]<\/code>. Note that in controller-based apps, these attributes trigger validations\nthat are performed during model binding, but in Minimal APIs, they are used only for documentation.<\/p>\n<p>See the <a href=\"https:\/\/learn.microsoft.com\/aspnet\/core\/fundamentals\/openapi\/include-metadata\">Include OpenAPI metadata<\/a> topic in the docs to learn more about adding metadata to your API methods and data.<\/p>\n<h3>Customize your documents<\/h3>\n<p>ASP.NET also provides a way to customize the generated OpenAPI document using &#8220;transformers&#8221;,\nwhich can operate on the entire document, on operations, or on schemas.\nTransformers are classes that implement the <code>IOpenApiDocumentTransformer<\/code>, <code>IOpenApiOperationTransformer<\/code>, or <code>IOpenApiSchemaTransformer<\/code> 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.\nThe OpenAPI document, operation, or schema passed to a transformer is a strongly-typed object\nusing the types from the <a href=\"https:\/\/learn.microsoft.com\/dotnet\/api\/microsoft.openapi.models\">Microsoft.OpenApi.Models<\/a> namespace.\nThe method performs the transformation &#8220;in place&#8221; by modifying the object it receives.<\/p>\n<p>Transformers are added by the <code>configureOptions<\/code> delegate parameter of the <code>AddOpenApi<\/code> call,\nand can specified as an instance of a class, as a DI-activated class, or as a delegate method.<\/p>\n<pre><code class=\"language-csharp\">builder.Services.AddOpenApi(options =&gt;\n{\n    \/\/ document transformer added as an instance of a class\n    options.AddDocumentTransformer(new MyDocumentTransformer());\n    \/\/ operation transformer added as a DI-activated class\n    options.AddOperationTransformer&lt;MyOperationTransformer&gt;();\n    \/\/ schema transformer added as a delegate method\n    options.AddSchemaTransformer((schema, context, cancellationToken)\n                            =&gt; Task.CompletedTask);\n});<\/code><\/pre>\n<p>One application of document transformers is to modify portions of the OpenAPI document outside the <code>paths<\/code> and <code>components.schemas<\/code> sections. For example, you could add a <code>contact<\/code> in the <code>info<\/code> element of the document like this:<\/p>\n<pre><code class=\"language-csharp\">builder.Services.AddOpenApi(options =&gt;\n{\n    options.AddDocumentTransformer((document, context, cancellationToken) =&gt;\n    {\n        document.Info.Contact = new OpenApiContact\n        {\n            Name = \"Contoso Support\",\n            Email = \"support@contoso.com\"\n        };\n        return Task.CompletedTask;\n    }\n});<\/code><\/pre>\n<p>Operation transformers can be used to modify individual operations in the document. An operation transformer is invoked\non every operation in the app, and it can choose to modify the operation or not. For example, you could add a <code>security<\/code> requirement to all operations that require authorization like this:<\/p>\n<pre><code class=\"language-csharp\">    options.AddOperationTransformer((operation, context, cancellationToken) =&gt;\n    {\n        if (context.Description.ActionDescriptor.EndpointMetadata.OfType&lt;IAuthorizeData&gt;().Any())\n        {\n            operation.Security = [new() { [\"Bearer\"] = [] }];\n        }\n        return Task.CompletedTask;\n    });<\/code><\/pre>\n<p>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.<\/p>\n<p>It is important to know that all transformers, including schema transformers, are invoked <em>before<\/em> schemas are converted to &#8220;$ref&#8221; references &#8212; a process discussed in the next section.<\/p>\n<p>The following example shows a simple schema transformer that sets the <code>format<\/code> field to <code>decimal<\/code> for any schema\nrepresenting a C# <code>decmial<\/code> value.<\/p>\n<pre><code class=\"language-csharp\">    options.AddSchemaTransformer((schema, context, cancellationToken) =&gt;\n    {\n        if (context.JsonTypeInfo.Type == typeof(decimal))\n        {\n            \/\/ default schema for decimal is just type: number.  Add format: decimal\n            schema.Format = \"decimal\";\n        }\n        return Task.CompletedTask;\n    });<\/code><\/pre>\n<h3>Customize schema reuse<\/h3>\n<p>After all transformers have been applied, the framework makes a pass over the document transferring certain schemas\nto the <code>components.schemas<\/code> section, replacing them with <code>$ref<\/code> references to the transferred schema.\nThis reduces the size of the document and makes it easier to read.<\/p>\n<p>The details of this processing are a bit complicated, and might change in future versions of .NET, but in general:<\/p>\n<ul>\n<li>Schemas for class\/record\/struct types are replaced with a <code>$ref<\/code> to a schema in <code>components.schemas<\/code> if they appear more than once in the document.<\/li>\n<li>Schemas for primitive types and standard collections are left &#8220;inline&#8221;.<\/li>\n<li>Schemas for enum types are always replaced with a <code>$ref<\/code> to a schema in to <code>components.schemas<\/code>.<\/li>\n<\/ul>\n<p>Typically the name of the schema in <code>components.schemas<\/code> is the name of the class\/record\/struct type, but in some circumstances a different name must be used.<\/p>\n<p>ASP.NET Core lets you customize which schemas are replaced with a <code>$ref<\/code> to a schema in <code>components.schemas<\/code> using the <code>CreateSchemaReferenceId<\/code> property of <code>OpenApiOptions<\/code>. This property is a delegate that takes a <code>JsonTypeInfo<\/code> object and returns the name of the schema in <code>components.schemas<\/code> that should be used for that type. The framework provides a default implementation of this delegate, <code>OpenApiOptions.CreateDefaultSchemaReferenceId<\/code>, that uses the name of the type, but you can replace it with your own implementation.<\/p>\n<p>As a simple example of this customization, you might choose to always inline enum schemas.\nThis is done by setting <code>CreateSchemaReferenceId<\/code> to a delegate that always returns <code>null<\/code> for enum types,\nand otherwise returns value from the default implementation.\nThe following code shows how to do this:<\/p>\n<pre><code class=\"language-csharp\">builder.Services.AddOpenApi(options =&gt;\n{\n    \/\/ Always inline enum schemas\n    options.CreateSchemaReferenceId = (type) =&gt; type.Type.IsEnum ? null : OpenApiOptions.CreateDefaultSchemaReferenceId(type);\n});<\/code><\/pre>\n<h3>Generating OpenAPI documents at build time<\/h3>\n<p>A feature that I think many .NET developers will find appealing is the option to generate the OpenAPI document at build time.\nGenerating the OpenAPI document as part of the build process makes it much easier to integrate with tools in your\nlocal development workflow or CI pipeline. For example, you can run a linter on the generated document to ensure it meets your organization&#8217;s standards, or you can use the document to generate client code or tests.<\/p>\n<p>Generating the OpenAPI document at build time is simple. Just add the <code>Microsoft.Extensions.ApiDescription.Server<\/code> package to your project. By default, the OpenAPI document is generated into the <code>obj<\/code> directory of your project, but you can customize the location of the generated document with the <code>OpenApiDocumentsDirectory<\/code> property. For example, to generate the document into the root directory of your project, add the following to your project file:<\/p>\n<pre><code class=\"language-xml\">&lt;PropertyGroup&gt;\n  &lt;OpenApiDocumentsDirectory&gt;.\/&lt;\/OpenApiDocumentsDirectory&gt;\n&lt;\/PropertyGroup&gt;<\/code><\/pre>\n<p>Note that build-time OpenAPI document generation works by launching the application&#8217;s entrypoint with an inert server implementation. This allows the framework to incorporate metadata that is only available at runtime, but could require\nsome changes in your application to work properly in certain build scenarios.<\/p>\n<p>See the <a href=\"https:\/\/learn.microsoft.com\/aspnet\/core\/fundamentals\/openapi\/aspnetcore-openapi?view=aspnetcore-9.0#generate-openapi-documents-at-build-time\">Generating OpenAPI at Build Time<\/a> topic in the documentation for more information.<\/p>\n<h2>Conclusion<\/h2>\n<p>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.<\/p>\n<p>We&#8217;d love to hear your feedback on this new feature. Please try it out and let us know what you think.<\/p>\n<p>Happy coding!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introducing the New OpenAPI Document Generation Feature in .NET 9. Let&#8217;s take a look at what it is, how to use it, and how it streamlines API development in .NET.<\/p>\n","protected":false},"author":68772,"featured_media":54730,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7509],"tags":[7797,7786,7802],"class_list":["post-54729","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-aspnetcore","tag-dotnet-9","tag-api","tag-openapi"],"acf":[],"blog_post_summary":"<p>Introducing the New OpenAPI Document Generation Feature in .NET 9. Let&#8217;s take a look at what it is, how to use it, and how it streamlines API development in .NET.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/54729","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/68772"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=54729"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/54729\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/54730"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=54729"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=54729"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=54729"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}