{"id":24467,"date":"2021-02-04T13:33:14","date_gmt":"2021-02-04T20:33:14","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/aspnet\/?p=24467"},"modified":"2021-10-26T19:22:22","modified_gmt":"2021-10-27T02:22:22","slug":"creating-discoverable-http-apis-with-asp-net-core-5-web-api","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/creating-discoverable-http-apis-with-asp-net-core-5-web-api\/","title":{"rendered":"Creating Discoverable HTTP APIs with ASP.NET Core 5 Web API"},"content":{"rendered":"<p>This month, we&#8217;ll be focusing on building HTTP APIs with .NET 5. We&#8217;ll explore a myriad of different tools, technologies, and services that make your API development experience more delightful. Each week, we&#8217;ll release a new post on this blog that goes into a separate area of building HTTP APIs with .NET, focusing mostly on using ASP.NET Core 5 Web API and the OpenAPI Specification together to build, publish, consume, and re-use well-described HTTP APIs. Here&#8217;s a glance at the upcoming series on building HTTP APIs using .NET on the ASP.NET team blog:<\/p>\n<ul>\n<li>Creating Discoverable HTTP APIs with ASP.NET Core 5 Web API (this post)<\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/aspnet\/open-source-http-api-packages-and-tools\/?WT.mc_id=dotnet-13135-bradyg\">Open-source HTTP API packages and Tools<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/aspnet\/generating-http-api-clients-using-visual-studio-connected-services\/?WT.mc_id=dotnet-13135-jogallow\">Generating HTTP API clients using Visual Studio Connected Services<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/aspnet\/app-building-with-azure-api-management-functions-power-apps-and-logic-apps\/?WT.mc_id=dotnet-13135-jogallow\">App Building with Azure API Management, Power Apps, and Logic Apps<\/a><\/li>\n<\/ul>\n<p>Whether APIs <em>are<\/em> your product, or they&#8217;re a facet of the topology you build by default in your daily consulting work or freelancing, the process of building, testing, and integrating APIs can appear daunting. The posts in this series will show you how .NET is a great choice, as it offers RESTful, serverless, and more modern gRPC and HTTP2\/3 investments. By the end, you&#8217;ll know some new techniques and conventions, and have some sample code and open-source projects to follow.<\/p>\n<h2>Thinking design-first<\/h2>\n<p>If you&#8217;ve ever referenced a SOAP Web Service within Visual Studio using right-click <strong>Add Web Reference<\/strong> or, once WCF appeared, <strong>Add Service Reference<\/strong>, you know the joy brought to your development experience by the tools and their support for a standard description format &#8211; the Web Service Definition Language (WSDL).<\/p>\n<p>The OpenAPI Specification has evolved as the leading industry convention for describing standard request\/response HTTP APIs, and a myriad of tools, open-source packages, and frameworks have been built atop ASP.NET Web API to make the process of building HTTP APIs simple. Whilst OpenAPI isn&#8217;t as strict or verbose as WSDL, the relaxed nature of the language makes for a wide variety of misuses and missed opportunities.<\/p>\n<p>The .NET community is still rich with an ecosystem of open-source packages and tools, some of which we use in the ASP.NET templates and Visual Studio tooling. In the ASP.NET Core 5 Web API space, there are a handful of packages developers can use to get going with an end-to-end development experience using OpenAPI specifications as a contract between the API and the clients:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.nuget.org\/packages\/Swashbuckle.AspNetCore\/?WT.mc_id=dotnet-13135-bradyg\">Swashbuckle.AspNetCore<\/a> &#8211; The most popular OpenAPI-generating package for ASP.NET Core developers. Used not only by the <a href=\"https:\/\/swagger.io\/tools\/swagger-codegen\/\">Swagger Codegen<\/a> project, but also by the ASP.NET Core 5 Web API templates (catch the <a href=\"https:\/\/youtu.be\/G7Le65Ln_Qk?WT.mc_id=dotnet-13135-bradyg\">HTTP APIs session from .NET Conf<\/a> where we highlight these updates to the Web API template). Swashbuckle emits Swagger\/OpenAPI 2.0, 3.0, and 3.0 YAML, and can output the Swagger UI test page to make testing and documenting your APIs easy. <\/li>\n<li><a href=\"https:\/\/github.com\/RicoSuter\/NSwag?WT.mc_id=dotnet-13135-bradyg\">NSwag<\/a> &#8211; NSwag is another fantastic choice for generating OpenAPI documents from ASP.NET Core 5 Web API, but the NSwag team has an entire toolchain complete with <a href=\"https:\/\/github.com\/RicoSuter\/NSwag\/wiki\/NSwagStudio?WT.mc_id=dotnet-13135-bradyg\">NSwagStudio<\/a>.<\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/web-api\/microsoft.dotnet-openapi?WT.mc_id=dotnet-13135-bradyg\">Microsoft.dotnet-openapi<\/a> &#8211; .NET Global Tool that can generate C# client SDK code for an OpenAPI specification.<\/li>\n<\/ul>\n<p>A well designed API is so much nicer to develop, maintain, and consume. By following a few simple conventions when building Web APIs that use these packages to describe your APIs, your APIs will be <strong>much<\/strong> more discoverable, integrate with other products and cloud services more easily, and in general, offer more usage scenarios.<\/p>\n<h2>The sample project<\/h2>\n<p>This post will use a <a href=\"https:\/\/github.com\/bradygaster\/Contoso.Online.Orders?WT.mc_id=dotnet-13135-bradyg\">sample Visual Studio Solution<\/a>. In the solution you&#8217;ll find a simple Web API project for Contoso Online Orders. We&#8217;ll build more onto the API over time, but just to give you a glimpse at the API&#8217;s shape, take a look at the Swagger UI page from the Web API project&#8217;s Debug experience.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2021\/02\/swagger-ui.png\" alt=\"Swagger UI page\" \/><\/p>\n<p>The solution comes with the API non-optimized for discoverability. Throughout the steps in this post, you&#8217;ll be shown how to use any of the Visual Studio family of IDEs to make changes to the projects so you&#8217;ll see a before-and-after experience of the API as it was at first, then improved over time by small incremental changes.<\/p>\n<p>The changes we&#8217;ll make can be summarized as:<\/p>\n<ul>\n<li>Making the OpenAPI specification more concise<\/li>\n<li>Inform consumers or integrators of all the potential request and response shapes and statuses that could happen<\/li>\n<li>Ensure that OpenAPI code generators and OpenAPI-consuming services can ingest my OpenAPI code and thus, call to my API<\/li>\n<\/ul>\n<p>With that, we&#8217;ll jump right in and see how some of the attributes built in to ASP.NET Core 5 Web API can make your APIs concise, right out of the box.<\/p>\n<h2>Produces \/ Consumes<\/h2>\n<p>The JSON code for the Web API project is, by default, rather verbose and, ironically, not very informative about what could happen when the API is called in various states. Take this <code>orders<\/code> operation, shown here. The C# code in my Web API controller will always output objects serialized in JSON format, but the OpenAPI specification is advertising other content types, too.<\/p>\n<pre><code class=\"json\">{\r\n  \"paths\": {\r\n    \"\/orders\": {\r\n      \"get\": {\r\n        \"tags\": [\r\n          \"Admin\"\r\n        ],\r\n        \"responses\": {\r\n          \"200\": {\r\n            \"description\": \"Success\",\r\n            \"content\": {\r\n              \"text\/plain\": {\r\n                \"schema\": {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"$ref\": \"#\/components\/schemas\/Order\"\r\n                  }\r\n                }\r\n              },\r\n              \"application\/json\": {\r\n                \"schema\": {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"$ref\": \"#\/components\/schemas\/Order\"\r\n                  }\r\n                }\r\n              },\r\n              \"text\/json\": {\r\n                \"schema\": {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"$ref\": \"#\/components\/schemas\/Order\"\r\n                  }\r\n                }\r\n              }\r\n            }\r\n          }\r\n        }\r\n      }\r\n    }\r\n  }\r\n}\r\n<\/code><\/pre>\n<p><a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/microsoft.aspnetcore.mvc.producesattribute?view=aspnetcore-5.0&amp;WT.mc_id=dotnet-13135-bradyg\">ProducesAttribute<\/a> is used to specify the content type of the output that will be sent by the API, and the corresponding <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/microsoft.aspnetcore.mvc.consumesattribute?view=aspnetcore-5.0&amp;WT.mc_id=dotnet-13135-bradyg\">ConsumesAttribute<\/a> is used to specify the request content types the API expects to receive. As described in the <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/web-api\/advanced\/formatting?view=aspnetcore-5.0#specify-a-format&amp;WT.mc_id=dotnet-13135-bradyg\">docs on formatting Web API output<\/a>, <code>Produces<\/code> and <code>Consumes<\/code> filters by either specifying the content-type you want to output:<\/p>\n<pre><code class=\"csharp\">[Produces(\"application\/json\")]\r\n[Consumes(\"application\/json\")]\r\n<\/code><\/pre>\n<p>As shown in the <a href=\"https:\/\/github.com\/bradygaster\/Contoso.Online.Orders?WT.mc_id=dotnet-13135-bradyg\">sample project<\/a> corresponding to this blog series, you can also use the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.net.mime.mediatypenames?view=net-5.0&amp;WT.mc_id=dotnet-13135-bradyg\">MediaTypeName<\/a> class to make it simpler to use well-known values for the media type. With the sample controllers, we want every request object and response object to be serialized as JSON. To facilitate this at the controller level, each controller is decorated with both the <code>Produces<\/code> and <code>Consumes<\/code> attributes. To each attribute is passed the well-known property <code>MediaTypeNames.Application.Json<\/code>, thus specifying that the only content type our API should use for both directions is <code>application\/json<\/code>.<\/p>\n<pre><code class=\"csharp\">    [Route(\"[controller]\")]\r\n    [ApiController]\r\n#if ProducesConsumes\r\n    [Produces(MediaTypeNames.Application.Json)]\r\n    [Consumes(MediaTypeNames.Application.Json)]\r\n#endif\r\n    public class AdminController : ControllerBase\r\n    {\r\n        \/\/ controller code\r\n    }\r\n<\/code><\/pre>\n<p>Since the code in the sample project is built to check for certain symbles using compiler directives, you can easily tweak the build configuration to include the Defined Constant value of <code>ProducesConsumes<\/code> to turn on the attributes in the Web API sample project code. In Visual Studio for Mac, constants can be added by double-clicking a <code>.csproj<\/code> file in the Solution Explorer to <a href=\"https:\/\/docs.microsoft.com\/visualstudio\/mac\/managing-solutions-and-project-properties?view=vsmac-2019&amp;WT.mc_id=dotnet-13135-bradyg\">open the project properties window<\/a>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2021\/02\/produces-consumes.png\" alt=\"Adding ProducesConsumes to the compiler settings.\" width=\"2500\" height=\"1463\" class=\"aligncenter size-full wp-image-24474\" \/><\/p>\n<p>Now, when the Web API project is re-built and run, the OpenAPI specification is considerably smaller, due in large part to the removal of the unnecessary request and response content nodes. The updated JSON, reflecting this change, now only shows the <code>application\/json<\/code> content type, thus making the API specification much more compact.<\/p>\n<pre><code class=\"json\">{\r\n  \"paths\": {\r\n    \"\/orders\": {\r\n      \"get\": {\r\n        \"tags\": [\r\n          \"Admin\"\r\n        ],\r\n        \"responses\": {\r\n          \"200\": {\r\n            \"description\": \"Success\",\r\n            \"content\": {\r\n              \"application\/json\": {\r\n                \"schema\": {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"$ref\": \"#\/components\/schemas\/Order\"\r\n                  }\r\n                }\r\n              }\r\n            }\r\n          }\r\n        }\r\n      }\r\n    }\r\n  }\r\n}\r\n<\/code><\/pre>\n<h2>HTTP Response Codes<\/h2>\n<p>Web API developers can make use of a variety of Action Result inheritors to send the appropriate HTTP status code to the calling client in addition to the object or operation implemented by the API. In the <code>GetProduct<\/code> method, for example, the code tries to get a product by ID. If the product is found, it is returned along with an HTTP OK via the <code>Ok<\/code> result. If the product isn&#8217;t found, the API returns an HTTP 404, with no payload.<\/p>\n<pre><code class=\"csharp\">[HttpGet(\"\/products\/{id}\")]\r\npublic async Task&lt;ActionResult&lt;Product&gt;&gt; GetProduct(int id)\r\n{\r\n    var product = StoreServices.GetProduct(id);\r\n\r\n    if(product == null)\r\n    {\r\n        return NotFound();\r\n    }\r\n    else\r\n    {\r\n        return Ok(product);\r\n    }\r\n}\r\n<\/code><\/pre>\n<p>However, the OpenAPI JSON for this method only shows the 200 response code, the default behavior for an HTTP GET. OpenAPI supports the notion of communicating all potential response codes an API could return, so we&#8217;re missing out on an opportunity to inform potential consumers or code generators on &#8220;what could happen&#8221; when the API method is called.<\/p>\n<pre><code class=\"json\">\"\/products\/{id}\": {\r\n  \"get\": {\r\n    \"tags\": [\r\n      \"Shop\"\r\n    ],\r\n    \"parameters\": [\r\n      {\r\n        \"name\": \"id\",\r\n        \"in\": \"path\",\r\n        \"required\": true,\r\n        \"schema\": {\r\n          \"type\": \"integer\",\r\n          \"format\": \"int32\"\r\n        }\r\n      }\r\n    ],\r\n    \"responses\": {\r\n      \"200\": {\r\n        \"description\": \"Success\",\r\n        \"content\": {\r\n          \"application\/json\": {\r\n            \"schema\": {\r\n              \"$ref\": \"#\/components\/schemas\/Product\"\r\n            }\r\n          }\r\n        }\r\n      }\r\n    }\r\n  }\r\n}\r\n<\/code><\/pre>\n<h2>Web API Conventions<\/h2>\n<p>Web API conventions, available in ASP.NET Core 2.2 and later,include a way to extract common API documentation and apply it to multiple actions, controllers, or all controllers within an assembly. Web API conventions are a substitute for decorating individual actions with [ProducesResponseType]. Since API Conventions are extensible, you could write your own to enforce more granular rules if needed. Common use cases of conventions would be to:<\/p>\n<ul>\n<li>Define the most common return types and status codes returned from a specific type of action.<\/li>\n<li>Identify actions that deviate from the defined standard.<\/li>\n<\/ul>\n<p>The sample Web API project&#8217;s <code>Program.cs<\/code> file is decorated with the API Convention attribute &#8211; this is an approach that will impact the output of every Web API controller in the assembly, but you can apply conventions more granularly if desired. <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/web-api\/advanced\/conventions?view=aspnetcore-5.0&amp;WT.mc_id=dotnet-13135-bradyg\">See the documentation<\/a> on Web API conventions for more details.<\/p>\n<pre><code class=\"csharp\">using Microsoft.AspNetCore.Hosting;\r\nusing Microsoft.Extensions.Hosting;\r\n\r\n#if ApiConventions\r\n[assembly: ApiConventionType(typeof(DefaultApiConventions))]\r\n#endif\r\n\r\nnamespace ContosoOnlineOrders.Api\r\n{\r\n    public class Program\r\n    {\r\n<\/code><\/pre>\n<p>For this second compiler change, here&#8217;s how to do the same thing in Visual Studio. You can also double-click any <code>.csproj<\/code> file in the Solution Explorer and manually enter it (see below).<\/p>\n<blockquote>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/aspnet\/wp-content\/uploads\/sites\/16\/2021\/02\/api-conventions.png\" alt=\"Add the ApiConventions build property\" width=\"2500\" height=\"1356\" class=\"aligncenter size-full wp-image-24473\" srcset=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/api-conventions.png 2500w, https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/api-conventions-300x163.png 300w, https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/api-conventions-1024x555.png 1024w, https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/api-conventions-768x417.png 768w, https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/api-conventions-1536x833.png 1536w, https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/api-conventions-2048x1111.png 2048w\" sizes=\"(max-width: 2500px) 100vw, 2500px\" \/><\/p>\n<\/blockquote>\n<p>Once you&#8217;ve made the change, re-building and running the Web API project will result with the OpenAPI specification being equipped with response code details for each of the API operations.<\/p>\n<pre><code class=\"json\">\"\/products\/{id}\": {\r\n  \"get\": {\r\n    \"tags\": [\r\n      \"Shop\"\r\n    ],\r\n    \"parameters\": [\r\n      {\r\n        \"name\": \"id\",\r\n        \"in\": \"path\",\r\n        \"required\": true,\r\n        \"schema\": {\r\n          \"type\": \"integer\",\r\n          \"format\": \"int32\"\r\n        }\r\n      }\r\n    ],\r\n    \"responses\": {\r\n      \"404\": {\r\n        \"description\": \"Not Found\",\r\n        \"content\": {\r\n          \"application\/json\": {\r\n            \"schema\": {\r\n              \"$ref\": \"#\/components\/schemas\/ProblemDetails\"\r\n            }\r\n          }\r\n        }\r\n      },\r\n      \"default\": {\r\n        \"description\": \"Error\",\r\n        \"content\": {\r\n          \"application\/json\": {\r\n            \"schema\": {\r\n              \"$ref\": \"#\/components\/schemas\/ProblemDetails\"\r\n            }\r\n          }\r\n        }\r\n      },\r\n      \"200\": {\r\n        \"description\": \"Success\",\r\n        \"content\": {\r\n          \"application\/json\": {\r\n            \"schema\": {\r\n              \"$ref\": \"#\/components\/schemas\/Product\"\r\n            }\r\n          }\r\n        }\r\n      }\r\n    }\r\n  }\r\n}\r\n<\/code><\/pre>\n<h2>Emit OpenAPIs operationId in Web API<\/h2>\n<p><a href=\"https:\/\/swagger.io\/docs\/specification\/about\/\">In the OpenAPI specification<\/a>, <code>operationId<\/code> is defined as &#8220;an optional unique string used to identify an operation. If provided, these IDs must be unique among all operations described in your API.&#8221; The <code>operationId<\/code> attribute is used essentially to provide a explicit string-based identifier for each operation in an API.<\/p>\n<p>Whilst the <code>operationId<\/code> attribute isn&#8217;t required according to the OpenAPI Specification, including it in your APIs offers significant improvements in the <strong>API Consumption<\/strong> experience &#8211; documentation, code-generation, and integration with a myriad of services.<\/p>\n<p>Take a look at a condensed version of the OpenAPI specification. This snapshot summarizes the API to the various endpoints, verbs, and operations offered by the API.<\/p>\n<pre><code class=\"json\">{\r\n  \"paths\": {\r\n    \"\/orders\": {\r\n      \"get\": {\r\n        \"tags\": [\r\n          \"Admin\"\r\n        ]\r\n      },\r\n      \"post\": {\r\n        \"tags\": [\r\n          \"Shop\"\r\n        ]\r\n      }\r\n    },\r\n    \"\/orders\/{id}\": {\r\n      \"get\": {\r\n        \"tags\": [\r\n          \"Admin\"\r\n        ]\r\n      }\r\n    },\r\n    \"\/products\/{id}\/checkInventory\": {\r\n      \"put\": {\r\n        \"tags\": [\r\n          \"Admin\"\r\n        ]\r\n      }\r\n    },\r\n    \"\/products\": {\r\n      \"post\": {\r\n        \"tags\": [\r\n          \"Admin\"\r\n        ]\r\n      },\r\n      \"get\": {\r\n        \"tags\": [\r\n          \"Shop\"\r\n        ]\r\n      }\r\n    }\r\n  }\r\n}\r\n<\/code><\/pre>\n<p>Each of the Web API Action Methods in the sample project is decorated with two variations of the Attribute Route. The first, wrapped in the <code>OperationId<\/code> compiler symbol, results in the name of the C# Action Method being automatically set as the <code>operationId<\/code> value. The <code>Name<\/code> property, when passed to the constructor of each HTTP verb filter, is used as the value of the <code>operationId<\/code> attribute in the generated OpenAPI specification document. The second lacks the <code>Name<\/code> property, which results in the <code>operationId<\/code> attribute value being omitted.<\/p>\n<pre><code class=\"csharp\">#if OperationId\r\n[HttpGet(\"\/orders\", Name = nameof(GetOrders))]\r\n#else\r\n[HttpGet(\"\/orders\")]\r\n#endif\r\npublic async Task&lt;ActionResult&lt;IEnumerable&lt;Order&gt;&gt;&gt; GetOrders()\r\n{\r\n    return Ok(StoreServices.GetOrders());\r\n}\r\n\r\n\r\n#if OperationId\r\n[HttpPost(\"\/orders\", Name = nameof(CreateOrder))]\r\n#else\r\n[HttpPost(\"\/orders\")]\r\n#endif\r\npublic async Task&lt;ActionResult&lt;Order&gt;&gt; CreateOrder(Order order)\r\n{\r\n    try\r\n    {\r\n        StoreServices.CreateOrder(order);\r\n        return Created($\"\/orders\/{order.Id}\", order);\r\n    }\r\n    catch\r\n    {\r\n        return Conflict();\r\n    }\r\n}\r\n<\/code><\/pre>\n<p>The 3rd and final compiler switch you&#8217;ll add to the sample project activates <code>operationId<\/code> generation. If you&#8217;re using Visual Studio for Mac or Windows, use one of the techniques shown earlier to set it. Or, if you&#8217;re in Visual Studio Code or another text editor, just edit the <code>.csproj<\/code> files in the solution (both of them) to include the <code>operationId<\/code> value (and to expect it during consumption and code-generation).<\/p>\n<pre><code class=\"xml\">&lt;PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' \"&gt;\r\n  &lt;DefineConstants&gt;TRACE;DEBUG;NET;NET5_0;NETCOREAPP;ProducesConsumes;ApiConventions;OperationId&lt;\/DefineConstants&gt;\r\n&lt;\/PropertyGroup&gt;\r\n<\/code><\/pre>\n<p>Once the <code>OperationId<\/code> symbol is set, the Action Methods on each of the Web API controllers emits <code>operationId<\/code> attribute values. Considering the condensed version of the OpenAPI specification from earlier, here&#8217;s the new OpenAPI spec inclusive with the <code>operationId<\/code> attributes.<\/p>\n<pre><code class=\"json\">{\r\n  \"paths\": {\r\n    \"\/orders\": {\r\n      \"get\": {\r\n        \"tags\": [\r\n          \"Admin\"\r\n        ],\r\n        \"operationId\": \"GetOrders\"\r\n      },\r\n      \"post\": {\r\n        \"tags\": [\r\n          \"Shop\"\r\n        ],\r\n        \"operationId\": \"CreateOrder\"\r\n      }\r\n    },\r\n    \"\/orders\/{id}\": {\r\n      \"get\": {\r\n        \"tags\": [\r\n          \"Admin\"\r\n        ],\r\n        \"operationId\": \"GetOrder\"\r\n      }\r\n    },\r\n    \"\/products\/{id}\/checkInventory\": {\r\n      \"put\": {\r\n        \"tags\": [\r\n          \"Admin\"\r\n        ],\r\n        \"operationId\": \"CheckInventory\"\r\n      }\r\n    },\r\n    \"\/products\": {\r\n      \"post\": {\r\n        \"tags\": [\r\n          \"Admin\"\r\n        ],\r\n        \"operationId\": \"CreateProduct\"\r\n      },\r\n      \"get\": {\r\n        \"tags\": [\r\n          \"Shop\"\r\n        ],\r\n        \"operationId\": \"GetProducts\"\r\n      }\r\n    }\r\n  }\r\n}\r\n<\/code><\/pre>\n<h3>Benefits in the Generated SDK Code<\/h3>\n<p>Human readability isn&#8217;t the only benefit. Code generators like the <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/web-api\/microsoft.dotnet-openapi?WT.mc_id=dotnet-13135-bradyg\">Microsoft.dotnet-openapi<\/a> tools, <a href=\"https:\/\/github.com\/RicoSuter\/NSwag?WT.mc_id=dotnet-13135-bradyg\">NSwag<\/a>, and <a href=\"https:\/\/github.com\/Azure\/autorest?WT.mc_id=dotnet-13135-bradyg\">AutoRest<\/a> operate more gracefully when OpenAPI specifications include the <code>operationId<\/code> attribute.<\/p>\n<p>Later in this blog series, we&#8217;ll take a look at how Visual Studio Connected Services makes use of the <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/web-api\/microsoft.dotnet-openapi?WT.mc_id=dotnet-13135-bradyg\">Microsoft.dotnet-openapi<\/a> tools to streamline C# client SDK code generation with one click. For now, imagine inheriting code for an ordering-process test that looked like the code below.<\/p>\n<pre><code class=\"csharp\">\/\/ create a product\r\nawait apiClient.ProductsAllAsync(new CreateProductRequest\r\n{\r\n    Id = 1000,\r\n    InventoryCount = 0,\r\n    Name = \"Test Product\"\r\n});\r\n\r\n\/\/ update a product's inventory\r\nawait apiClient.CheckInventory2Async(1,                           \r\nnew InventoryUpdateRequest\r\n    {\r\n        CountToAdd = 50,\r\n        ProductId = 1000\r\n    });\r\n\r\n\/\/ get all products\r\nawait apiClient.ProductsAsync();\r\n\r\n\/\/ get one product\r\nawait apiClient.Products2Async(1000);\r\n\r\n\/\/ create a new order\r\nGuid orderId = Guid.NewGuid();\r\n\r\nawait apiClient.OrdersAsync(new Order\r\n{\r\n    Id = orderId,\r\n    Items = new CartItem[]\r\n    {\r\n        new CartItem { ProductId = 1000, Quantity = 10 }\r\n    }\r\n});\r\n\r\n\/\/ get one order\r\nawait apiClient.Orders2Async(orderId);\r\n\r\n\/\/ get all orders\r\nawait apiClient.OrdersAllAsync();\r\n\r\n\/\/ check an order's inventory\r\nawait apiClient.CheckInventoryAsync(orderId);\r\n\r\n\/\/ ship an order\r\nawait apiClient.ShipAsync(orderId);\r\n<\/code><\/pre>\n<p>Without comments, most of the methods &#8211; especially <code>OrdersAsync<\/code> and <code>Orders2Async<\/code> &#8211; are discoverable only when looking at their arguments. This generated code suffers from poor readability or discoverability. This code was also generated from an OpenAPI spec lacking in <code>operationId<\/code> values. So, the code generator had to make a host of assumptions based on the value of the <code>tags<\/code>, the name of the operation, and so on.<\/p>\n<p>But once the OpenAPI specification has been augmented with values for each of the operations&#8217; <code>operationId<\/code> attribute, the code generator has more information and can generate a more concise SDK that any developer can use and discover with little effort.<\/p>\n<pre><code class=\"csharp\">\/\/ create a product\r\nawait apiClient.CreateProductAsync(new CreateProductRequest\r\n{\r\n    Id = 1000,\r\n    InventoryCount = 0,\r\n    Name = \"Test Product\"\r\n});\r\n\r\n\/\/ update a product's inventory\r\nawait apiClient.UpdateProductInventoryAsync(1,\r\n    new InventoryUpdateRequest\r\n    {\r\n        CountToAdd = 50,\r\n        ProductId = 1000\r\n    });\r\n\r\n\/\/ get all products\r\nawait apiClient.GetProductsAsync();\r\n\r\n\/\/ get one product\r\nawait apiClient.GetProductAsync(1000);\r\n\r\n\/\/ create a new order\r\nGuid orderId = Guid.NewGuid();\r\n\r\nawait apiClient.CreateOrderAsync(new Order\r\n{\r\n    Id = orderId,\r\n    Items = new CartItem[]\r\n    {\r\n        new CartItem { ProductId = 1000, Quantity = 10 }\r\n    }\r\n});\r\n\r\n\/\/ get one order\r\nawait apiClient.GetOrderAsync(orderId);\r\n\r\n\/\/ get all orders\r\nawait apiClient.GetOrdersAsync();\r\n\r\n\/\/ check an order's inventory\r\nawait apiClient.CheckInventoryAsync(orderId);\r\n\r\n\/\/ ship an order\r\nawait apiClient.ShipOrderAsync(orderId);\r\n<\/code><\/pre>\n<h2>Summary<\/h2>\n<p>This first post, whilst somewhat theoretical and design-oriented, is very important to the subsequent phases of building and using HTTP APIs. With these simple steps taken early, your OpenAPI specification document will more thoroughly and concisely describe your API and make it simpler for consumers to use.<\/p>\n<p>We look forward to exploring the world of HTTP APIs with .NET with this and other projects this month! Feel free to provide feedback on the article, the series, and as always, feel enabled to use our GitHub repositories to submit issues and ideas if you&#8217;re inspired to make the product better through feedback.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This month, we&#8217;ll be focusing on building HTTP APIs with .NET 5. We&#8217;ll explore a myriad of different tools, technologies, and services that make your API development experience more delightful. Each week, we&#8217;ll release a new post on this blog that goes into a separate area of building HTTP APIs with .NET, focusing mostly on [&hellip;]<\/p>\n","protected":false},"author":2046,"featured_media":36351,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[197,7509],"tags":[32,7230],"class_list":["post-24467","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-aspnet","category-aspnetcore","tag-asp-net-core","tag-web-api"],"acf":[],"blog_post_summary":"<p>This month, we&#8217;ll be focusing on building HTTP APIs with .NET 5. We&#8217;ll explore a myriad of different tools, technologies, and services that make your API development experience more delightful. Each week, we&#8217;ll release a new post on this blog that goes into a separate area of building HTTP APIs with .NET, focusing mostly on [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/24467","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\/2046"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=24467"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/24467\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/36351"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=24467"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=24467"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=24467"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}