Sarıkaya Dev Logo

Auto‑generate type‑safe OpenAPI clients in .NET 8 with source generators

Mahmut Sarıkaya 4 min read 15 Views 0
Auto‑generate type‑safe OpenAPI clients in .NET 8 with source generators

Why write API wrappers by hand when you can generate them safely?

Every seasoned .NET developer has spent at least an hour fixing a mismatched JSON property or a missing query parameter in a hand‑crafted HttpClient call. The frustration grows exponentially when the same API evolves and the client code must be updated manually. .NET 8 introduces source generators that turn an OpenAPI specification into a compile‑time, type‑safe client, eliminating runtime surprises and aligning perfectly with HttpClientFactory.

Project prerequisites and initial setup

Before diving into code, ensure you are running .NET 8 SDK (released November 2023) and have a recent version of Visual Studio 2022 (17.8) or VS Code with the C# extension. Create a fresh solution to keep the example isolated:

dotnet new sln -n OpenApiDemo
mkdir src && cd src
dotnet new console -n DemoClient
dotnet sln add DemoClient/DemoClient.csproj

The console project will host the generated client and a simple test call.

Adding the AutoClient source generator

The official package that powers compile‑time client generation is Microsoft.Extensions.Http.AutoClient. Install it with a single command:

dotnet add src/DemoClient/DemoClient.csproj package Microsoft.Extensions.Http.AutoClient

This package brings the [AutoClient] and HTTP verb attributes that the generator reads to emit concrete implementations.

Defining the OpenAPI contract

Place your OpenAPI document (for example, github-openapi.json) in the src/DemoClient folder. The generator looks for files with .json or .yaml extensions referenced in the AutoClient attribute. A minimal snippet of the spec might describe the /repos/{owner}/{repo} endpoint used later.

Creating a typed client interface

Inside the project, add a new file IGitHubClient.cs and write the following interface. Notice the use of [AutoClient] to bind the OpenAPI file and the verb attributes to describe each operation.

using System.Threading.Tasks;
using Microsoft.Extensions.Http.AutoClient;

namespace DemoClient;

[AutoClient("GitHub", BaseAddress = "https://api.github.com", OpenApiDocument = "github-openapi.json")]
public interface IGitHubClient
{
    [Get("/repos/{owner}/{repo}")]
    Task<Repository> GetRepositoryAsync(string owner, string repo);
}

public class Repository
{
    public string Name { get; set; }
    public string Full_Name { get; set; }
    public int Stargazers_Count { get; set; }
}

The generator will produce a concrete class named GitHubClient that implements IGitHubClient and registers it with the DI container.

Wiring the client with HttpClientFactory

Open Program.cs and replace its content with the snippet below. The call to builder.Services.AddAutoClient<IGitHubClient>() registers the generated implementation as a typed client, letting you inject it wherever needed.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using DemoClient;

var host = Host.CreateDefaultBuilder()
    .ConfigureServices((context, services) =>
    {
        services.AddAutoClient<IGitHubClient>();
        services.AddHostedService<Worker>();
    })
    .Build();

await host.RunAsync();

public class Worker : IHostedService
{
    private readonly IGitHubClient _client;
    public Worker(IGitHubClient client) => _client = client;
    public async Task StartAsync(CancellationToken ct)
    {
        var repo = await _client.GetRepositoryAsync("dotnet", "runtime");
        Console.WriteLine($"Repo: {repo.Full_Name}, Stars: {repo.Stargazers_Count}");
    }
    public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}

Because the client is built at compile time, the method signatures are fully typed. If the OpenAPI document changes—say a new required header is added—the generator will emit a compilation error, forcing you to adjust the code before the application even runs.

Practical tips for a smooth experience

  • Keep the OpenAPI file under source control; the generator runs on every build, so any accidental change is caught immediately.
  • Use the [Header] attribute to inject static headers (e.g., User-Agent) without hard‑coding them in each method.
  • Leverage the built‑in Polly policies by chaining .AddPolicyHandler(...) to the typed client registration for retry and circuit‑breaker logic.
  • When targeting large specifications, limit the generator to the subset you need by using the Operations property of [AutoClient].

Performance considerations and debugging

Since the generated client uses HttpClientFactory under the hood, socket exhaustion is avoided, and DNS changes are respected. For debugging, enable the built‑in logging provider with builder.Logging.SetMinimumLevel(LogLevel.Information). The generated code also respects the HttpClient timeout configured in appsettings.json, so you can fine‑tune latency without touching the client code.

Conclusion

Source generators in .NET 8 turn the traditionally error‑prone task of writing OpenAPI clients into a compile‑time guarantee. By pairing Microsoft.Extensions.Http.AutoClient with HttpClientFactory, you get a type‑safe, DI‑friendly client that evolves together with the API contract. The result is fewer runtime bugs, clearer code, and a smoother developer experience—exactly what modern microservice ecosystems demand.

Sources

  • Microsoft Docs – .NET 8 source generators
  • Microsoft Docs – HttpClientFactory and typed clients
  • NSwag official documentation (for OpenAPI spec handling)

Author: Mahmut Sarıkaya — sarikayadev.com

Tags: #dotnet 8 #source generators #openapi #type-safe client #httpclientfactory
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

2 + 3 =