Add automatic OpenAPI client code generation to .NET 6 apps using dotnet-openapi, NSwag and service references

OpenAPI defines a way for web services to clearly define their API for automatic and correct client library generation, and with NSwag, clients for these APIs can be automatically generated for C#. What’s more, rather than generating the source code for these clients manually, a service reference can be added to your .csproj file to generate these clients transparently and automatically at development-time and build-time, —essentially It Just Works! This is truly an amazing (although not specifically unique,) benefit of the .NET tooling ecosystem; any IDE which plugs into the common .NET C# language backend (VS, Code, Rider, you name it) will immediately see and present the generated API client classes and their methods in type suggestions, without anything files having to be compiled or included manually by the developer.

Today I’ll write on how to go about this via a dotnet command-line tool; applicable regardless of the IDE or development platform you use. Whilst Visual Studio users get a simple (and obvious) wizard for adding these API service references, the information regarding the platform-agnostic command-line version seems to be scattered across the ASP.NET Core docs, and personally I felt it was quite obscure and challenging to find. Hopefully this blog post speeds up this process for any other developers going through these same steps for the first time.

Getting started

To get started, you’ll need the OpenAPI/Swagger JSON document for the API you wish to target. For my project, I wanted to connect to the Public Transport Victoria Timetable API. I navigated to the Swagger interface for the V3 API, where the ‘Swagger JSON’ document was then linked. I then placed a copy of the document in my project directory.

You’ll also need to decide where the generated API client will live inside your project’s namespace, and what the name of the API client class will be. In my case, I decided to name the generated API client class PtvClient, and place it under a Ptv namespace beneath my project’s main namespace. Whilst this naming may seem redundant, NSwag will also spit out a bunch of entity classes as defined by the API, as well as the client class, so I felt it was best to keep it all organised underneath it’s own specific namespace in my project.

Adding the reference using the ‘dotnet-openapi’ global tool

We’ll now use the dotnet openapi command to add the service reference to our .csproj file. This does not come bundled with .NET, however it is a quick dotnet tool install away.

$ dotnet tool install -g Microsoft.dotnet-openapi
You can invoke the tool using the following command: dotnet-openapi
Tool 'microsoft.dotnet-openapi' (version '6.0.1') was successfully installed.

Once the global tool has been installed, we can use it to add the service reference. Note: the name of the OpenAPI document in my case is ptv-v3.json.

$ dotnet openapi add file ptv-v3.json

The command should execute without any output and add the relevant packages and OpenAPIReference entry to your project’s csproj file. We’ll then need to edit the project file to define the class name and namespace of the generated API client, as well as any other optional NSwag parameters. Find the newly-added block in your .csproj file that references the API–it should look like this;

  <ItemGroup>
    <OpenApiReference Include="ptv-v3.json" />
  </ItemGroup>

…and specify a relevant ClassName and Namespace.

  <ItemGroup>
    <OpenApiReference Include="ptv-v3.json" Namespace="VicTransportApp.Ptv" ClassName="PtvClient" />
  </ItemGroup>

In my case, I specified the class name to be PtvClient, and the namespace to be VicTransportApp.Ptv. I also then specified some optional generation configuration, as seen below;

    <OpenApiReference Include="ptv-v3.json" Namespace="VicTransportApp.Ptv" ClassName="PtvClient">
      <Options>/OperationGenerationMode:SingleClientFromOperationId</Options>
    </OpenApiReference>

This may be required if the API specification you’re targeting has a large amount of duplicate names, as the PTV one appears to. The SingleClientFromOperationId OperationGenerationMode seemed to generate a client without any errors for me–analysing the output code shows a single class containing all methods (PtvClient,) and then separate classes for the different entity types returned by the API.

What it looks like…

Once you give your project a build, MSBuild should get NSwag to work it’s magic, and you’ll end up with a ${API_NAME}Client.cs or similarly-named file in your obj build outputs directory. This will be automatically referenced by MSBuild—no extra work is needed to pull the generated code into your project.

Using the generated API client is as simple as placing a using on the output namespace, and instantiating the main client class with a HttpClient.

using VicTransportApp.Ptv;

namespace VicTransportApp;

public class TestClass
{
    public async Task MainAsync()
    {
        var client = new PtvClient(new HttpClient());
        var result = await client.Search_SearchAsync("test search query", /*...*/);
    }
}

Further reading

I stumbled across this .NET global tool in this Docs article, however as I alluded to in the opening of this post, it did take me a decent while to find this information, and I felt it didn’t quite explain everything needed to get this off the ground, especially for someone without any prior experience in API client code generation.

If you found this article helpful, please don’t hesitate to let me know in the comments below! Thanks for reading 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s