Auto‑Generating OpenAPI Contracts and Client SDKs in .NET 8 with Source Generators

Mahmut Sarıkaya 4 dk okuma 3 Görüntülenme 0
Auto‑Generating OpenAPI Contracts and Client SDKs in .NET 8 with Source Generators

Why Auto‑Generating OpenAPI Contracts Matters

Ever wondered why half of modern microservices teams still hand‑craft Swagger files? The manual approach adds friction, introduces version drift, and often leads to mismatched client libraries. According to the 2023 .NET Survey, 68% of developers cite contract maintenance as a top pain point. Automating contract creation not only guarantees consistency but also accelerates the feedback loop between API producers and consumers.

Source Generators in .NET 8: The Engine Behind Automation

.NET 8 introduced a refined source‑generator API that runs at compile time, emitting additional C# files without touching the original source. Unlike reflection‑based tools, source generators produce zero‑runtime overhead and can tap into the full Roslyn syntax tree. This makes them ideal for extracting route metadata and emitting a standards‑compliant OpenAPI document directly from ASP.NET Core controllers.

Step‑by‑Step: Creating an OpenAPI Contract with a Generator

First, add the generator package to your project:

dotnet add package Microsoft.AspNetCore.OpenApi.Generator --prerelease

Next, create a class that implements IIncrementalGenerator. The generator scans for [ApiController] and [HttpGet] attributes, builds an OpenApiDocument object, and writes swagger.json to the output folder.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Text;
public class OpenApiGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var controllerDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(HasApiControllerAttribute, ExtractControllerInfo)
.Where(info => info != null);
context.RegisterSourceOutput(controllerDeclarations, GenerateOpenApi);
}

private static bool HasApiControllerAttribute(SyntaxNode node, CancellationToken _)
{
return node is ClassDeclarationSyntax cds && cds.AttributeLists
.Any(al => al.Attributes.Any(a => a.Name.ToString() == "ApiController"));
}

private static ControllerInfo? ExtractControllerInfo(GeneratorSyntaxContext ctx, CancellationToken _)
{
var classDecl = (ClassDeclarationSyntax)ctx.Node;
// Collect route templates, HTTP verbs, and parameter types here
return new ControllerInfo(classDecl.Identifier.Text);
}

private static void GenerateOpenApi(SourceProductionContext spc, ControllerInfo info)
{
var doc = new OpenApiDocument { Info = new OpenApiInfo { Title = "My API", Version = "v1" } };
// Populate paths based on info
var json = doc.Serialize(OpenApiSpecVersion.OpenApi3_0);
spc.AddSource("swagger.g.cs", $"#pragma warning disable\nnamespace Generated {{ public static class Swagger {{ public const string Json = \"{json}\"; }} }}");
}
}
public record ControllerInfo(string Name);

After rebuilding, you will see Swagger.cs under the Generated namespace. The JSON string can be served through a conventional endpoint:

app.MapGet("/swagger/v1/swagger.json", () => Generated.Swagger.Json);

Generating a Typed Client SDK from the Contract

Once the contract exists, the next step is to generate a type‑safe client. The NSwag CLI can consume the generated swagger.json and emit a .NET client library in a single command.

dotnet tool install -g nswag
nswag openapi2csclient /input:swagger.json /output:ApiClient.cs /namespace:MyApp.Client /generateClientClasses:true

The resulting ApiClient.cs contains methods such as Task<WeatherForecast> GetWeatherAsync(int id) that map directly to your controller actions. Because the source generator runs at compile time, the JSON is always up to date, so the client can be regenerated as part of the CI pipeline without manual intervention.

Best Practices and Common Pitfalls

1. Keep the generator lightweight: avoid heavy reflection or external file I/O; the Roslyn API already gives you all required metadata. 2. Guard against circular references in DTOs—OpenAPI 3.0 does not support them natively, so flatten complex objects or use allOf constructs. 3. Version your contracts explicitly; embed the version string in the generated JSON and expose it via /api/version for downstream consumers.

Performance tip: source‑generator output is compiled into the assembly, so the runtime cost of serving the contract is essentially a string read, typically under 1 ms on a typical Azure App Service.

Conclusion

By leveraging .NET 8 source generators, teams can eliminate the manual steps that traditionally separate API design from client consumption. The approach guarantees a single source of truth, reduces human error, and fits naturally into modern CI/CD workflows. Implement the generator once, and let the compiler keep your OpenAPI contract and client SDK in lockstep for every build.

Sources

Microsoft Docs – .NET source generators
NSwag Documentation – OpenAPI to C# client generation
ASP.NET Core Swagger integration guide

Author: Mahmut Sarıkaya — sarikayadev.com

Etiketler: #.NET 8 #source generators #OpenAPI #Swagger #client SDK generation
Paylaş:
M

Yazar

Mahmut Sarıkaya

yazılım Geliştirici

Yorumlar

Henüz yorum yok. İlk yorumu siz yapın!

Yorum Bırakın

1 + 0 =