Auto generated WebAPI client library with NSwag

, Author: Cezary Piątek


In the era of microservices and distributed systems, web browser applications written in JavaScript are not the only consumers of the REST API. Today, more and more often this type of communication is used to connect backend services, too. Integrating two services using REST protocol doesn’t require any form of shared contract which makes the process extremely easy when both sides are developed in different technologies but it comes at a price. At the end of the day, we always need to write some set of classes that act as a client proxy, and if there is more than one consumer of a given service, we need to repeat that work in every one of them. Another problem is with propagating information about changes in the API and adjusting all clients to those changes. Taking into account those disadvantages, it’s worth considering publishing a client proxy alongside the service itself, especially if it’s possible to generate it automatically. There’s a couple of existing projects on the market that allow generating C# client from OpenAPI (Swagger) specification like:

swagger-codegen and openapi-generator are Java based CLI tools, so they require JVM to run but they allow to generate Rest API clients in a verity of programming languages. NSwag is built with dotnet but supports only C# and TypeScript clients generation. Anyway, I decided to use NSwag because it was the easiest one to integrate with MsBuild and allows for generating clients directly from WebAPI server assembly. Although there’s plenty of articles about generating C# client using NSwag, it took me a whole day to put all the necessary pieces together, so I decided to write my own description for future reference.

Project organization 🔗︎

  • SampleService - this is AspCore WebAPI project providing REST endpoints
  • SampleService.ApiClient - responsible for producing dll as well as nuget package with REST client based on the SampleService project’s output assembly
  • SampleService.ComponentTest - contains components test for SampleService using in-memory test server. Tested API is always called via client generated by SampleService.ApiClient project

Project dependency diagram

Steps to configure automated client generation 🔗︎

Prepare nswag.json manifest 🔗︎

nswag.json defines a set of parameters required by NSwag for generating client code like input assembly and output file path, as well as other different options allowing to adjust the shape of output code to our needs. The easiest way to generate the manifest file is to use Windows UI application called NSwag Studio. Here’s the minimal configuration required to correctly generate C# client:

Nswag Studio sample configuration

Generate Outputs button allows us to preview the generated client source code. After achieving the desired output, we should save the current configuration as nswag.json file directly in SampleService.ApiClient project’s directory - NSwag should automatically convert all paths (assembly and references) to the relative form.

In addition to options selected on the screenshot I also use the following settings to customize client proxy code:

{
  "codeGenerators": {
    "openApiToCSharpClient": {      
      "generateClientInterfaces": true,
      "useBaseUrl": false,
      "generateBaseUrlProperty": false,
      "generateOptionalParameters": true,
      "parameterArrayType": "System.Collections.Generic.IReadOnlyList",
      "responseArrayType": "System.Collections.Generic.IReadOnlyList",      
      "generateOptionalPropertiesAsNullable": true,
      "generateNullableReferenceTypes": true,
      "output": "$(Output)",
    }
  }
}

Configure build of ApiClient project 🔗︎

Having nswag.json manifest adjusted to our requirement we can configure automatic Api Client generation during the build. This can be easily achieved with NSwag.MSBuild package and a little bit of MsBuild scripting. Here’s a content of SampleService.ApiClient.csproj file:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.1</TargetFramework>
    <!--Automatically generate nuget package with client library-->
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
  </PropertyGroup>
  <ItemGroup>
    <!--Add required nuget packages necessary for ApiClient build-->
    <PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
    <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
    <PackageReference Include="NSwag.MSBuild" Version="13.7.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
  <ItemGroup>
    <!--Add build time reference to SampleService project-->
    <ProjectReference Include="..\SampleService\SampleService.csproj">
      <Private>False</Private>
      <ReferenceOutputAssembly>false</ReferenceOutputAssembly>
      <SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
    </ProjectReference>
  </ItemGroup>
  <!--Add build step responsible for generating the source code of client api and including it to compilation-->
  <Target Name="GenerateApiClientSourceCode" BeforeTargets="CoreCompile;PrepareResource">
    <Exec Command="$(NSwagExe_Core31) run nswag.json /variables:Configuration=$(Configuration),Output=$(IntermediateOutputPath)\ApiClient.generated.cs" />
    <ItemGroup>
      <Compile Include="$(IntermediateOutputPath)\ApiClient.generated.cs" />
    </ItemGroup>
  </Target>
</Project>

In the GenerateApiClientSourceCode build target we are passing additional values with /variables parameter to NSwag CLI. Those values can be accessed in nswag.json manifest using $() notation, as shown on the following excerpt:

{
  "runtime": "NetCore31",
  "defaultVariables": "Configuration=Debug,Output=./ApiClient.generated.cs",
  "documentGenerator": {
    "webApiToOpenApi": {
      "assemblyPaths": [
        "../SampleService/bin/$(Configuration)/netcoreapp3.1/SampleService.dll"
      ],
      "referencePaths": [
        "../SampleService/bin/$(Configuration)/netcoreapp3.1/"
      ],
    }
  },
  "codeGenerators": {
    "openApiToCSharpClient": {
      "output": "$(Output)"
    }
  }
}

Defaults can be defined with "defaultVariables" - this is very useful if we are going to edit this file in NSwag Studio in the future.

Since now, on every build, NSwag should generate automatically the source code of API client which is later compiled into SampleService.ClientApi.dll library and packed as a nuget.

Here are the benefits of configuring automatic client generation in this way:

  • The client code is continuously synchronized with the API.
  • We can easily examine client source code because it’s saved in IntermediateOutputPath (in most cases it’s just obj directory).
  • We can add an additional code which is using generated client api directly in the SampleService.ApiClient project.

The last fact can be used for providing an implementation of partial methods defined in generated client class like: UpdateJsonSerializerSettings, PrepareRequest, and ProcessResponse. We can also extend generated client by inheritance or with extension methods:

public static class WeatherForecastClientExtensions
{
    public static Task<IReadOnlyList<WeatherForecast>?> GetForTodayAsync(this IWeatherForecastClient client, CancellationToken cancellationToken = default) 
        => client.GetAsync(1, cancellationToken);
    
    public static Task<IReadOnlyList<WeatherForecast>?> GetForNextWeekAsync(this IWeatherForecastClient client, CancellationToken cancellationToken = default) 
        => client.GetAsync(7, cancellationToken);
}

Summary 🔗︎

Thanks to NSwag tooling we can very easily configure automatic WebAPI client generation. A basic setup presented in this blog post is available as a Sample Project on my Github.


Products recommended for highly effective .NET Developers:


comments powered by Disqus

See Also