Client Generation in .NET 7 🤖

Photo by Toa Heftiba on Unsplash

Client Generation in .NET 7 🤖

Using NSwag to consistently generate C# clients

A bit of history 📚

Many developers have gone through the hassle of implementing clients toward external and internal APIs. Creating a client implementation of an API is tedious and error-prone if done manually. The solution to this problem is standards for how to structure an API. 👌

In the days of SOAP APIs, we were faced with WSDL files describing the interface and how to use it. Nowadays the most common API structure is the Representational State Transfer (REST) API.

Both solutions are meant to be language agnostic meaning that the interface should not dictate implementation technology, and most importantly exposes the functionality in a way that makes it possible to automatically generate a client implementation. 🦾

Open API specification

A widely adopted standard for REST API implementation is the Open API specification. Originally created by Tony Tam, later acquired by the company SmartBear, the standard is as of today overseen by the OpenAPI Initiative, which is a part of the open-source collaboration project The Linux Foundation. 🐧

Petstore API as an example 🐶

In this demonstration, I am going to create a client for a Petstore API by swagger. The API exposes open API documentation at: https://petstore.swagger.io/v2/swagger.json

Tooling 🛠

In this demonstration, I am going to use NSwag as a tool for client generation. Many products for client generation exist but I like using NSwag as it is possible to run as a .NET program, meaning that the client generation can be of the project in which the client is going to be used.

To get started I create a new console application project in .NET and install the NSwag.CodeGeneration.CSharp NuGet package.

First off I need to download the open API document from the Petstore API.

var url = "https://petstore.swagger.io/v2/swagger.json";

using (var client = new WebClient())
{
    client.DownloadFile(url, "openapi.json");
}

var document = await OpenApiDocument.FromFileAsync("openapi.json");

Secondly, I am specifying the settings for the client generator. I like structuring my project using Clean Architecture, and I, therefore, define that the client will belong to the Infrastructure project of my application.

var settings = new CSharpClientGeneratorSettings
{
    ClassName = "AutogeneratedPetStoreClient",
    CSharpGeneratorSettings = 
    {
        Namespace = "MyApplication.Infrastructure.Clients.Petstore"
    },
    UseBaseUrl = false,
    OperationNameGenerator = new SingleClientFromPathSegmentsOperationNameGenerator()
};

Finally, I generate the client.

var generator = new CSharpClientGenerator(document, settings);    
var code = generator.GenerateFile();

File.WriteAllText("AutogeneratedPetStoreClient.cs", code);

When running this program, a AutogeneratedPetStoreClient.cs file is written to the bin directory.

Using the client 🤖

I now have a consistent and documented way of generating a client for the Petstore API. It is important to make any code changes in the autogenerated client, as this will be overwritten if a new client is generated.

Authorize the client 🔐

The Petstore API requires an API key as authorization.

The generated client is a partial class that exposes methods that can be overridden. For authorization, I am going to create a new file next to the autogenerated client named AutogeneratedPetStoreClientAuthorization. In this file, I will create another partial class overriding the PrepareRequest. In this method, I am going to inject the API Key into the Auth header before each request.

public partial class AutogeneratedPetStoreClient
{

    private const string _apiKey;

    [ActivatorUtilitiesConstructor]
    public AutogeneratedPetStoreClient(HttpClient client, IConfiguration _config) : this(client)
    {
        _apiKey = _config["apikey"]
    }

    partial void PrepareRequest(HttpClient client, HttpRequestMessage request,
        string url)
    {
        request.Headers.Authorization = new AuthenticationHeaderValue("ApiKey", _apiKey);
    }
}

Conclusion 🙌

Next time I generate a new client, the authorization implementation will stay, even though the client is overridden, making it possible to consistently regenerate the client without changes to the implementation. 🚀

Did you find this article valuable?

Support Mathias Milter by becoming a sponsor. Any amount is appreciated!