{"id":14887,"date":"2023-07-25T00:42:08","date_gmt":"2023-07-25T07:42:08","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/cse\/?p=14887"},"modified":"2024-07-18T11:49:19","modified_gmt":"2024-07-18T18:49:19","slug":"maintain-api-client-with-nswag-model-gen","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ise\/maintain-api-client-with-nswag-model-gen\/","title":{"rendered":"Maintaining API Clients With NSwag Model Generation"},"content":{"rendered":"<h2>Overview<\/h2>\n<p>If you are familiar with implementing APIs, or Application Programming Interfaces, you probably know of <a href=\"https:\/\/www.openapis.org\/\">OpenAPI<\/a> or <a href=\"https:\/\/swagger.io\/\">Swagger<\/a>. In this post, we want to share some information on how we used <a href=\"https:\/\/github.com\/RicoSuter\/NSwag\">NSwag<\/a>, an OpenAPI .NET toolchain, to generate C# models in code and improve the maintainability of a <a href=\"https:\/\/github.com\/dotnet\/core\">.NET Core<\/a> API client. First, we\u2019ll introduce the primary role of an API client, touch on our project challenges, and then describe how our project used NSwag to solve those challenges.<\/p>\n<h2>Role of an API Client<\/h2>\n<p>API clients can take different forms, such as libraries, SDKs (Software Development Kits), command-line tools, or custom-built applications. In most forms, the primary role of an API client is to act as an API that abstracts the complexities of working with one or more underlying APIs. Therefore, they often play a dual role as both an API client and an API.<\/p>\n<p>With this role in mind, API clients are often focused on providing a positive developer experience. In some cases, an API client may even provide a higher-level abstraction that is more aligned with the domain of the developer. In any case, there&#8217;s no escaping the fact that they are dependent on underlying APIs. In one way or another, they must preserve compatibility with relevant changes that occur with underlying APIs. As a result, an API client requires regular maintenance to keep up with the required changes.<\/p>\n<h2>The Challenge<\/h2>\n<p>How do we meet the challenge of maintaining an API client? How do we make managing change easier for the developer? We faced this challenge in our project because we needed to build a .NET Core API client, in the form of a front-end web API client, that communicated with several internal back-end APIs. In this case, the front-end web API client was an API client to each of the internal back-end APIs. However, the client was also an API to the front-end web application that consumed it. Therefore, the front-end web API client was both an API client and an API.<\/p>\n<p>When given the challenge of maintaining an API client, you are off to a great start if the underlying set of backing APIs are designed well. In our case, we had full control over the underlying set of backing APIs. We also adhered to the common practice of exposing a Swagger or OpenAPI spec for each underlying API to make it easier for our front-end web API client to integrate. For guidance on API design, consider reading <a href=\"https:\/\/devblogs.microsoft.com\/cse\/2023\/05\/08\/design-api-first-with-typespec\">A Technical Journey into API Design-First: Best Practices and Lessons Learned &#8211; CSE Developer Blog (microsoft.com)<\/a>.<\/p>\n<p>However, a well-designed underlying API is not enough. Developers maintaining the client still must decide on how tightly coupled it should be to each of it&#8217;s underlying APIs. In our case, the front-end web API required using back-end models, defined in the OpenAPI spec, for serializing data into API requests and deserializing API responses like custom errors. Therefore, it was the evolving model definitions of each back-end API that were driving integration requirements and not the full set of underlying API endpoints. Due to this, we needed help with maintaining model compatibility between the front-end web models and the back-end models defined in the OpenAPI spec. In our case, by first focusing on minimizing downtime from model changes, we could tighten the developer loop, and the front-end web API client would become properly scoped to the changes it should care about in the underlying APIs.<\/p>\n<p>Fortunately, a major benefit of an OpenAPI spec is that it unlocks access to an ecosystem of tools that we could explore for maintaining model compatibility between the front-end web C# models and the back-end models defined in each OpenAPI spec. Therefore, we explored code generation tools that could take models defined in a OpenAPI spec and convert them into the C# models required by the client. Preferably, the code generation tool would also skip unnecessary code generation of HTTP client code endpoints defined in a OpenAPI spec.<\/p>\n<h2>Exploring Solutions<\/h2>\n<p>For auto-generating the front-end web C# models, we first investigated leveraging <a href=\"https:\/\/github.com\/OpenAPITools\/openapi-generator-cli\">Openapi-generator-cli<\/a>. Using this CLI, the following command successfully generated the C# models we needed from the OpenAPI models defined in the spec.<\/p>\n<ul>\n<li><strong>Command<\/strong>:\n<pre><code class=\"language-powershell\">openapi-generator-cli generate -g csharp-netcore -i http:\/\/localhost:000\/swagger\/v1\/swagger.json --global-property models --additional-properties targetFramework=net6.0 -o autogenmodels <\/code><\/pre>\n<\/li>\n<\/ul>\n<p>However, by default, it output an additional number of files that did not meet our needs. To make matters worse, our project used <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=ChrisDahlberg.StyleCop\">StyleCop<\/a> to enforce formatting and style but the C# code that this tool generated was violating our StyleCop rules by default. Therefore, we decided to explore NSwag.<\/p>\n<ul>\n<li><strong>Output<\/strong>:\n<pre><code class=\"language-powershell\"># Openapi-generator-cli generated models\r\nsrc \/\r\n|\u2500\u2500\u2500 .vscode\r\n|\u2500\u2500\u2500 ...\r\n\u2514\u2500\u2500\u2500 Models \r\n        \u251c\u2500\u2500\u2500docs # markdown per model\r\n        |    |\u2500\u2500\u2500 Model1.md\r\n        |    \u2514\u2500\u2500\u2500 Model2.md\r\n        \u251c\u2500\u2500\u2500src\r\n        |    |\u2500\u2500\u2500 Org.OpenAPITools\/Model # file per model\r\n        |    |                      |\u2500\u2500\u2500 Model1.cs\r\n        |    |                      \u2514\u2500\u2500\u2500 Model2.cs\r\n        |    |\u2500\u2500\u2500 Org.OpenAPITools.Test\r\n        |    |                      |\u2500\u2500\u2500 Model1Tests.cs\r\n        |    |                      \u2514\u2500\u2500\u2500 Model2Tests.cs\r\n        \u2514\u2500\u2500\u2500 ...<\/code><\/pre>\n<\/li>\n<\/ul>\n<h2>Model Generation With NSwag<\/h2>\n<p>Our first go at NSwag was with <a href=\"https:\/\/github.com\/RicoSuter\/NSwag\/wiki\/NSwagStudio\">NSwag Studio<\/a>. It\u2019s a GUI (Graphical User Interface) that can be configured to auto-generate client code using OpenAPI specs as input. We explored NSwag Studio and successfully generated C# models for our front-end web API. Here, all models from an OpenAPI spec were isolated to a single cs file and no additional files were generated. Essentially, the outcome of this tool, per back-end API, was an auto-generated cs file hosting C# models. This was a cleaner outcome than we experienced with Openapi-generator-cli.<\/p>\n<ul>\n<li><strong>Output<\/strong>:\n<pre><code class=\"language-powershell\"># NSwag generated C# models\r\nsrc \/\r\n|\u2500\u2500\u2500 .vscode\r\n|\u2500\u2500\u2500 ...\r\n\u2514\u2500\u2500\u2500 Models # holds a cs models file per OpenAPI spec\r\n        \u251c\u2500\u2500\u2500AutoGen_API1_Models.cs\r\n        \u251c\u2500\u2500\u2500AutoGen_API2_Models.cs\r\n        \u251c\u2500\u2500\u2500AutoGen_API3_Models.cs\r\n        \u2514\u2500\u2500\u2500..<\/code><\/pre>\n<\/li>\n<\/ul>\n<p>To take it a step further, we explored using the <a href=\"https:\/\/github.com\/RicoSuter\/NSwag\/wiki\/CommandLine\">NSwag CLI<\/a>. However, we ultimately decided to use NSwag through the <a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/core\/tools\/\">.NET CLI<\/a> for a more streamlined .NET developer experience. Here are the steps we took to configure NSwag for generating C# models within our .NET Core front-end web API.<\/p>\n<h3>Configuring NSwag<\/h3>\n<ol>\n<li>From your API client\u2019s csproj directory, download dotnet NSwag CLI and take note of the default runtime version (ex. <code>Net70<\/code>). For more information on available commands and what they mean, visit the <a href=\"https:\/\/github.com\/RicoSuter\/NSwag\/wiki\/CommandLine\">NSwag CommandLine Wiki<\/a>.\n<pre><code class=\"language-powershell\"># Downloads dotnet's project specific tooling file \r\ndotnet new tool-manifest \r\n# Downloads dotnet's Nswag CLI tool \r\ndotnet tool install NSwag.ConsoleCore \r\n# Outputs version information. This info will be used to satisfy the NSwag $nswagRuntime argument. \r\ndotnet nswag version\r\n# Generates the `nswag.json` config file. \r\ndotnet nswag new <\/code><\/pre>\n<\/li>\n<li>Configure the <code>nswag.json<\/code> configuration file using the following example. Use the following parameters. Also ensure the code generator is openApiToCSharpClient. For further help configuring an NSwag Configuration Document, visit <a href=\"https:\/\/github.com\/RicoSuter\/NSwag\/wiki\/NSwag-Configuration-Document\">NSwag Configuration Document Wiki<\/a>.<strong>Parameters<\/strong>:\n<ul>\n<li><em>NswagRuntime<\/em>: This is the runtime that NSwag should execute under. The value (ex. <code>Net70<\/code>) will be passed to the <code>nswag.json<\/code> file to satisfy the runtime prop (i.e. <code>\"runtime\": \"$(NswagRuntime)\"<\/code>).<\/li>\n<li><em>ApiServiceName<\/em>: This is the path and filename that should be autogenerated. The value (ex. <code>API1<\/code>) will be passed to the <code>nswag.json<\/code> file to satisfy the output prop (i.e. <code>\"output\": \"Models\/AutoGen_$(ApiServiceName)_Models.cs\"<\/code>).<\/li>\n<li><em>ApiSwaggerUrl<\/em>: This is the URL for the OpenAPI spec of a dependent service. The value (ex. <code>http:\/\/API1\/swagger\/v1\/swagger.json<\/code>) will be passed to the <code>nswag.json<\/code> file to satisfy the URL prop (i.e. <code>\"url\": \"$(ApiSwaggerUrl)\"<\/code>).<\/li>\n<li><em>ClientNamespace<\/em>: This namespace will be created for autogenerated models. The value (ex. <code>The.FrontEndAPI.Namespace.Models<\/code>) will be passed to the <code>nswag.json<\/code> file to satisfy the namespace prop (i.e. <code>\"namespace\": \"$(ClientNamespace)\"<\/code>).<\/li>\n<\/ul>\n<pre><code class=\"language-json\">{\r\n\"runtime\": \"$(NswagRuntime)\",\r\n\"documentGenerator\": {\r\n    \"fromDocument\": {\r\n    \"url\": \"$(ApiSwaggerUrl)\",\r\n    \"output\": \"swagger.json\",\r\n    \"newLineBehavior\": \"Auto\"\r\n        }\r\n    },\r\n\"codeGenerators\": {\r\n    \"openApiToCSharpClient\": {\r\n        \"generateClientClasses\": false,\r\n        \"generateClientInterfaces\": false,\r\n        \"injectHttpClient\": false,\r\n        \"disposeHttpClient\": false,\r\n        \"protectedMethods\": [],\r\n        \"generateExceptionClasses\": false,\r\n        \"exceptionClass\": \"ApiException\",\r\n        \"wrapDtoExceptions\": true,\r\n        \"useHttpClientCreationMethod\": false,\r\n        \"httpClientType\": \"System.Net.Http.HttpClient\",\r\n        \"useHttpRequestMessageCreationMethod\": false,\r\n        \"useBaseUrl\": false,\r\n        \"generateBaseUrlProperty\": false,\r\n        \"generateSyncMethods\": false,\r\n        \"generatePrepareRequestAndProcessResponseAsAsyncMethods\": false,\r\n        \"exposeJsonSerializerSettings\": false,\r\n        \"clientClassAccessModifier\": \"public\",\r\n        \"typeAccessModifier\": \"public\",\r\n        \"generateContractsOutput\": false,\r\n        \"parameterDateTimeFormat\": \"s\",\r\n        \"parameterDateFormat\": \"yyyy-MM-dd\",\r\n        \"generateUpdateJsonSerializerSettingsMethod\": false,\r\n        \"useRequestAndResponseSerializationSettings\": false,\r\n        \"serializeTypeInformation\": false,\r\n        \"queryNullValue\": \"\",\r\n        \"className\": \"{controller}Client\",\r\n        \"operationGenerationMode\": \"MultipleClientsFromOperationId\",\r\n        \"additionalNamespaceUsages\": [],\r\n        \"additionalContractNamespaceUsages\": [],\r\n        \"generateOptionalParameters\": false,\r\n        \"generateJsonMethods\": false,\r\n        \"enforceFlagEnums\": false,\r\n        \"parameterArrayType\": \"System.Collections.Generic.IEnumerable\",\r\n        \"parameterDictionaryType\": \"System.Collections.Generic.IDictionary\",\r\n        \"responseArrayType\": \"System.Collections.Generic.ICollection\",\r\n        \"responseDictionaryType\": \"System.Collections.Generic.IDictionary\",\r\n        \"wrapResponses\": false,\r\n        \"wrapResponseMethods\": [],\r\n        \"generateResponseClasses\": false,\r\n        \"responseClass\": \"SwaggerResponse\",\r\n        \"namespace\": \"$(ClientNamespace)\",\r\n        \"requiredPropertiesMustBeDefined\": true,\r\n        \"dateType\": \"System.DateOnly\",\r\n        \"anyType\": \"object\",\r\n        \"dateTimeType\": \"System.DateTimeOffset\",\r\n        \"timeType\": \"System.TimeOnly\",\r\n        \"timeSpanType\": \"System.TimeSpan\",\r\n        \"arrayType\": \"System.Collections.Generic.ICollection\",\r\n        \"arrayInstanceType\": \"System.Collections.ObjectModel.Collection\",\r\n        \"dictionaryType\": \"System.Collections.Generic.IDictionary\",\r\n        \"dictionaryInstanceType\": \"System.Collections.Generic.Dictionary\",\r\n        \"arrayBaseType\": \"System.Collections.ObjectModel.Collection\",\r\n        \"dictionaryBaseType\": \"System.Collections.Generic.Dictionary\",\r\n        \"classStyle\": \"Poco\",\r\n        \"jsonLibrary\": \"NewtonsoftJson\",\r\n        \"generateDefaultValues\": true,\r\n        \"generateDataAnnotations\": true,\r\n        \"excludedTypeNames\": [],\r\n        \"excludedParameterNames\": [],\r\n        \"handleReferences\": false,\r\n        \"generateImmutableArrayProperties\": true,\r\n        \"generateImmutableDictionaryProperties\": true,\r\n        \"jsonSerializerSettingsTransformationMethod\": \"NewtonsoftJson\",\r\n        \"inlineNamedArrays\": false,\r\n        \"inlineNamedDictionaries\": false,\r\n        \"inlineNamedTuples\": true,\r\n        \"inlineNamedAny\": false,\r\n        \"generateDtoTypes\": true,\r\n        \"generateOptionalPropertiesAsNullable\": true,\r\n        \"generateNullableReferenceTypes\": false,\r\n        \"output\": \"Models\/AutoGen_$(ApiServiceName)_Models.cs\",\r\n        \"newLineBehavior\": \"Auto\"\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<\/li>\n<li>Per dependent API service, run the dotnet NSwag CLI tool against the <code>nswag.json<\/code> file. The output will be in a <code>Models\/<\/code> folder with a C# file name that reflects the service name. For example, if <code>API1<\/code> is provided as a value to the <code>ApiServiceName<\/code> parameter, a file called <code>AutoGen_API1_Models.cs<\/code> will be generated.<strong>Commands<\/strong>:\n<ul>\n<li>Powershell Example:\n<pre><code class=\"language-powershell\">### Sets variables\r\n$NswagRuntime = \"Net70\"; $ApiServiceName = \"API1\"; $ApiSwaggerUrl = \"http:\/\/API1\/swagger\/v1\/swagger.json\"; $ClientNamespace = \"The.FrontEndAPI.Namespace.Models\"\r\n### Auto-generates api client models by running against an http OpenApi spec\r\ndotnet nswag run nswag.json \/variables:NswagRuntime=$NswagRuntime,ApiServiceName=$ApiServiceName,ApiSwaggerUrl=$ApiSwaggerUrl,ClientNamespace=$ClientNamespace<\/code><\/pre>\n<\/li>\n<li>Bash Example:\n<pre><code class=\"language-bash\">### Sets variables\r\nNswagRuntime=\"Net70\" ApiServiceName=\"API1\" ApiSwaggerUrl=\"http:\/\/API1\/swagger\/v1\/swagger.json\" ClientNamespace=\"The.FrontEndAPI.Namespace.Models\"\r\n### Auto-generates api client models by running against an http OpenAPI spec\r\ndotnet nswag run nswag.json \/variables:NswagRuntime=$NswagRuntime,ApiServiceName=$ApiServiceName,ApiSwaggerUrl=$ApiSwaggerUrl,ClientNamespace=$ClientNamespace<\/code><\/pre>\n<\/li>\n<\/ul>\n<\/li>\n<li>Ensure the folders, models and namespaces are created as expected.\n<pre><code class=\"language-powershell\"># NSwag generated models\r\nsrc \/\r\n|\u2500\u2500\u2500 .vscode\r\n|\u2500\u2500\u2500 ...\r\n\u2514\u2500\u2500\u2500 Models # holds a cs models file per OpenAPI spec\r\n        \u251c\u2500\u2500\u2500AutoGen_API1_Models.cs\r\n        \u251c\u2500\u2500\u2500AutoGen_API2_Models.cs\r\n        \u251c\u2500\u2500\u2500AutoGen_API3_Models.cs\r\n        \u2514\u2500\u2500\u2500..<\/code><\/pre>\n<\/li>\n<\/ol>\n<h2>Additional Considerations With NSwag Code Generation<\/h2>\n<h3>Generating Client Code<\/h3>\n<p>If you require auto-generating the full client code and not just the models, consider NSwag\u2019s Github tutorial titled <a href=\"https:\/\/github.com\/RicoSuter\/NSwag\/blob\/master\/docs\/tutorials\/GenerateProxyClientWithCLI\/generate-proxy-client.md#a-how-to-generating-the-service-client-proxy-code\">A How-To: Generating The Service Client Proxy Code.<\/a>. We did not rely on this tutorial but it might work for your project. It\u2019s also important to note that the tutorial uses the NSwag CLI directly and not through the dotnet CLI.<\/p>\n<h3>Preventing C# Model Name Collisions Using Namespaces<\/h3>\n<p>It may be the case that auto-generating C# models results in naming collisions if the ClientNamespace is shared. To prevent this from happening when auto-generating your C# models, first, make sure that the value given to your ClientNamespace parameter will not be reused, then, alias your using statements for additional clarity in your client code base.<\/p>\n<ul>\n<li>Here\u2019s an example of aliasing to prevent naming collisions.\n<pre><code class=\"language-csharp\">using API1 = The.APIClient.Models.API1;\r\nusing API2 = The.APIClient.Models.API2;\r\n\r\npublic class Example\r\n{\r\n    public void ExampleMethod()\r\n    {\r\n        API1.ExampleModel exampleModel1 = new API1.ExampleModel();\r\n        API2.ExampleModel exampleModel2 = new API2.ExampleModel();\r\n    }\r\n}<\/code><\/pre>\n<\/li>\n<\/ul>\n<h3>Troubleshooting Serialization Hurdles<\/h3>\n<p>It may be the case that NSwag is serializing data in a way that\u2019s not useful. One option may be to change the serialization library defined in the configuration file. In this article, the default is <a href=\"https:\/\/www.newtonsoft.com\/json\">NewtonsoftJson<\/a> but there are alternative libraries that can be used. For example, <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/standard\/serialization\/system-text-json-overview\">System.Text.Json<\/a> is a library that can be used instead of NewtonsoftJson. To change the serialization library, change the value of the <code>jsonLibrary<\/code> property in the <code>nswag.json<\/code> file.<\/p>\n<ul>\n<li>Here\u2019s an example of changing the serialization library to System.Text.Json.\n<pre><code class=\"language-json\">{\r\n...\r\n\"codeGenerators\": {\r\n    \"openApiToCSharpClient\": {\r\n        ...\r\n        \"jsonLibrary\": \"System.Text.Json\", \r\n        ...\r\n        \"jsonSerializerSettingsTransformationMethod\": \"System.Text.Json\",\r\n        ...\r\n    }\r\n}}<\/code><\/pre>\n<\/li>\n<\/ul>\n<p>Another option is to override the default serialization behavior of your configured serialization library. In the following example, by default, NSwag attributes a DateFormatConverter (a DateTime type converter) to DateOnly Types. This is a known <a href=\"https:\/\/github.com\/RicoSuter\/NSwag\/issues\/4062\">NSwag DateFormatConverter Issue<\/a>. Inevitably, this throws an error because it\u2019s expecting the wrong type when serializing at runtime. Therefore, a custom serializer must be implemented to prevent the error. Here\u2019s an example of the error and the fix.<\/p>\n<ul>\n<li>The Unwanted Behavior (Default Attribute)\n<pre><code class=\"language-csharp\">[Newtonsoft.Json.JsonProperty(\"Date\", Required = Newtonsoft.Json.Required.Default)]\r\n[Newtonsoft.Json.JsonConverter(typeof(DateFormatConverter))]\r\npublic System.DateOnly? Date { get; set; }<\/code><\/pre>\n<\/li>\n<li>The Fix (Custom ContractResolver For Fixing DateTimeOnly)\n<pre><code class=\"language-csharp\">internal class ContractResolverToFixDateOnly : DefaultContractResolver\r\n{\r\n    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)\r\n    {\r\n        var property = base.CreateProperty(member, memberSerialization);\r\n\r\n        if (property.PropertyType == typeof(DateOnly))\r\n        {\r\n            property.Converter = null;\r\n        }\r\n\r\n        if (property.PropertyType == typeof(DateOnly?))\r\n        {\r\n            property.Converter = null;\r\n        }\r\n\r\n        return property;\r\n    }\r\n}<\/code><\/pre>\n<pre><code class=\"language-csharp\">    var settings = new JsonSerializerSettings\r\n    {\r\n        ContractResolver = new ContractResolverToFixDateOnly(),\r\n    };\r\n    \/\/ A model with a DateOnly property.\r\n    var model = JsonConvert.DeserializeObject&lt;Model&gt;(responseBody, settings);<\/code><\/pre>\n<\/li>\n<\/ul>\n<h2>Conclusion<\/h2>\n<p>By leveraging NSwag&#8217;s code generation capabilities, we simplified the process of maintaining the API client entities. It helped ensure compatibility with evolving API models which overall made it easier to decouple front-end functionality from back-end concerns in our front-end API client use case.<\/p>\n<p>In summary, NSwag code generation is a valuable tool for maintaining API clients. It enabled easier management of change and promoted a better developer experience. By leveraging OpenAPI specifications with NSwag, developers can streamline the process of keeping API clients up to date and maintainable.<\/p>\n<h2>Acknowledgements<\/h2>\n<p>Special thanks to <a href=\"https:\/\/www.linkedin.com\/in\/peter-lasne-99b42a\/\">Peter Lasne<\/a>, <a href=\"https:\/\/www.linkedin.com\/in\/eng-keong-david-lee\/\">David Lee<\/a> and the remainder of the team for findings that helped shaped this article.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Our team used NSwag, an OpenAPI .NET toolchain, to generate C# models in code and improve the maintainability of a .NET Core API client for a project. This post describes what lead to the decision along with guidance on how to use Nswag to generate C# models in code.<\/p>\n","protected":false},"author":120342,"featured_media":14888,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[3427,3398,3420,3401,3423,3424,113,3428,3378,3426,3429,3421,3404,3425,3422],"class_list":["post-14887","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cse","tag-net-core","tag-api","tag-api-client","tag-api-design","tag-api-management","tag-api-versioning","tag-c","tag-command-line","tag-dotnet","tag-models","tag-newtonsoft","tag-nswag","tag-openapi","tag-openapi-generator-cli","tag-swagger"],"acf":[],"blog_post_summary":"<p>Our team used NSwag, an OpenAPI .NET toolchain, to generate C# models in code and improve the maintainability of a .NET Core API client for a project. This post describes what lead to the decision along with guidance on how to use Nswag to generate C# models in code.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/14887","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/users\/120342"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/comments?post=14887"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/14887\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media\/14888"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media?parent=14887"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/categories?post=14887"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/tags?post=14887"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}