AutoHttpClient.Generator is an AOT-safe, compile-time typed HTTP client for .NET. Annotate an interface with [HttpClient], decorate methods with [Get], [Post], [Put], [Delete], or [Patch], and the generator emits a strongly-typed implementation plus DI registration at build time.
- Compile-time generated clients — no dynamic proxy generation, no reflection-heavy dispatch layer
- AOT-safe — generated C# calls
HttpClientdirectly, so it works cleanly with native AOT scenarios - Minimal ceremony — plain interfaces plus attributes, no hand-written wrappers
- DI-ready —
AddAutoHttpClients()registers every generated client forIServiceCollection - Strongly typed — route values, query parameters, headers, and JSON bodies all come from your method signature
- Refit is ergonomic, but relies on runtime plumbing and generated proxy behavior that is less predictable in AOT-focused deployments
- RestSharp is a runtime HTTP abstraction with reflection-oriented configuration rather than compile-time emitted clients
- AutoHttpClient.Generator keeps everything as generated source in your build output: explicit, trim-friendly, and zero-reflection
dotnet add package AutoHttpClient.GeneratorThen register the generated clients:
builder.Services.AddAutoHttpClients();usingAutoHttpClient;[HttpClient]publicinterfaceIOrdersApi{[Get("/api/orders/{id}")]Task<Order?>GetOrderAsync(intid,CancellationTokenct=default);[Get("/api/orders")]Task<List<Order>>GetOrdersAsync([Query("status")]string?status=null,CancellationTokenct=default);[Post("/api/orders")]Task<Order>CreateOrderAsync([Body]CreateOrderRequestrequest,CancellationTokenct=default);[Put("/api/orders/{id}")]Task<Order>UpdateOrderAsync(intid,[Body]UpdateOrderRequestrequest,CancellationTokenct=default);[Delete("/api/orders/{id}")]TaskDeleteOrderAsync(intid,CancellationTokenct=default);[Get("/api/orders/{id}/status")]Task<HttpResponseMessage>GetOrderStatusRawAsync(intid,CancellationTokenct=default);}Register the generated implementation:
builder.Services.AddAutoHttpClients();This emits an internal sealed client implementation and a DI registration similar to:
services.AddHttpClient<IOrdersApi,OrdersApiClient>();AutoHttpClient.Generator classifies parameters using these rules:
| Parameter style | Behavior |
|---|---|
[Body] | Serialized as JSON request content |
[Query("name")] | Added to the query string using the provided name |
[Query] or unattributed non-route parameter | Added to the query string using the parameter name |
[Header("X-Name")] | Added as an HTTP header |
| Route parameter | Any parameter whose name appears in the route template, e.g. {id} |
CancellationToken | Passed through to HttpClient and JSON helpers |
[Get("/api/orders")]Task<List<Order>>GetOrdersAsync([Query("status")]string?status=null,intpage=1,CancellationTokenct=default);[Post("/api/orders")]Task<Order>CreateOrderAsync([Body]CreateOrderRequestrequest,[Header("X-Tenant")]stringtenant,CancellationTokenct=default);[Get("/api/orders/{id}")]Task<Order?>GetOrderAsync(intid,CancellationTokenct=default);| Return type | Generated behavior |
|---|---|
Task | Sends the request and calls EnsureSuccessStatusCode() |
Task<T> | Sends the request, ensures success, and deserializes JSON with ReadFromJsonAsync<T>() |
Task<T?> | Same as Task<T> but preserves nullable result types |
Task<HttpResponseMessage> | Returns the raw response without EnsureSuccessStatusCode() |
You can configure a base address directly on the interface attribute:
usingAutoHttpClient;[HttpClient(BaseAddress="https://api.example.com")]publicinterfaceIOrdersApi{[Get("/api/orders")]Task<List<Order>>GetOrdersAsync(CancellationTokenct=default);}The generated DI registration configures the typed client:
services.AddHttpClient<IOrdersApi,OrdersApiClient>(client =>{client.BaseAddress=newUri("https://api.example.com");});AutoHttpClient.Generator now includes a small repo-side scaffolding tool for converting an OpenAPI/Swagger JSON document into a partial interface decorated with AutoHttpClient attributes.
Run it with:
dotnet run --project tools/AutoHttpClient.OpenApiScaffold -- --input swagger.json --output IMyApiClient.g.cs --namespace MyApp.Clients --interface-name IMyApiClientThe generated file is a one-time scaffold that you add to your project, then the existing AutoHttpClient.Generator source generator consumes it normally.
[HttpClient]or[HttpClient(BaseAddress = "...")]when the spec declares a simple server URL[Get],[Post],[Put],[Delete],[Patch]based on each OpenAPI operation- Route parameters as normal method parameters
- Query parameters as
[Query("name")] - Request bodies as
[Body] Task<T>return types using referenced schema names where possible
- Optimized for common OpenAPI 3 JSON documents
- Swagger/OpenAPI 2 documents may work for basic paths/operations, but v3 is the primary target
- Best support is for JSON request/response bodies with named schemas, simple path/query/header parameters, and standard HTTP verbs
- Inline/anonymous schemas fall back to
JsonElement(or collections/dictionaries of known types where possible) - Advanced OpenAPI features such as
oneOf,anyOf, callbacks, multipart form uploads, and full DTO generation are not scaffolded yet - Named schemas are used as C# type names in the generated interface; you still need matching DTO types in your project
| Feature | AutoHttpClient.Generator | Refit | RestSharp |
|---|---|---|---|
| Compile-time generated client | ✅ | Partial/runtime proxy behavior | ❌ |
| AOT-safe | ✅ | ❌ | |
| Zero reflection dispatch | ✅ | ❌ | |
Native HttpClient typed client DI | ✅ | ✅ | |
| Interface-first API | ✅ | ✅ | ❌ |
| OpenAPI/Swagger scaffolding tool | ✅ (repo tool) | ✅ | |
| Build-time diagnostics | ✅ | Limited | ❌ |
| Code | Severity | Message |
|---|---|---|
AH001 | Warning | Method on a [HttpClient] interface has no HTTP method attribute and will not be generated. |
AH002 | Warning | Route template parameter has no matching method parameter. |
AH003 | Error | Method has multiple [Body] parameters; only one is allowed. |
The package emits these attributes at post-initialization time:
HttpClientAttributeGetAttributePostAttributePutAttributeDeleteAttributePatchAttributeBodyAttributeQueryAttributeHeaderAttribute
AutoHttpClient.Generator uses the same interface-first approach as Refit. Migration is mostly a find-and-replace of attributes.
dotnet add package AutoHttpClient.Generator
dotnet remove package Refit
dotnet remove package Refit.HttpClientFactory// Before (Refit)usingRefit;publicinterfaceIOrdersApi{[Get("/api/orders/{id}")]Task<Order?>GetOrderAsync(intid,CancellationTokenct=default);[Post("/api/orders")]Task<Order>CreateOrderAsync([Body]CreateOrderRequestrequest,CancellationTokenct=default);[Get("/api/orders")]Task<List<Order>>GetOrdersAsync([AliasAs("status")]string?status=null);}// After (AutoHttpClient.Generator)usingAutoHttpClient;[HttpClient]publicinterfaceIOrdersApi{[Get("/api/orders/{id}")]Task<Order?>GetOrderAsync(intid,CancellationTokenct=default);[Post("/api/orders")]Task<Order>CreateOrderAsync([Body]CreateOrderRequestrequest,CancellationTokenct=default);[Get("/api/orders")]Task<List<Order>>GetOrdersAsync([Query("status")]string?status=null);}// Before (Refit)builder.Services.AddRefitClient<IOrdersApi>().ConfigureHttpClient(c =>c.BaseAddress=newUri("https://api.example.com"));// After (AutoHttpClient.Generator)[HttpClient(BaseAddress="https://api.example.com")]publicinterfaceIOrdersApi{ ...}builder.Services.AddAutoHttpClients();| Refit | AutoHttpClient.Generator |
|---|---|
[Get("/path")] | [Get("/path")] |
[Post("/path")] | [Post("/path")] |
[Put("/path")] | [Put("/path")] |
[Delete("/path")] | [Delete("/path")] |
[Patch("/path")] | [Patch("/path")] |
[Body] | [Body] |
[AliasAs("name")] | [Query("name")] |
[Header("X-Name")] | [Header("X-Name")] |
[HeaderCollection] | Not supported |
[Authorize] | Use [Header("Authorization")] |
[HeaderCollection]dictionary headers[Multipart]/[AttachmentName]for multipart form uploadsIObservable<T>return types- Custom
JsonSerializerSettingsper method
For projects using any of these heavily, hold off on migrating until support lands.
🌐 Full suite overview: swevo.github.io
| Package | Description |
|---|---|
| AutoLog.Generator | Compile-time high-performance logging — [Log(Level, Message)] on a partial method generates LoggerMessage.Define. AOT-safe. |
| AutoDispatch.Generator | Compile-time CQRS dispatcher — [Handler] generates a strongly-typed IDispatcher. No MediatR, no reflection. |
| AutoWire | Compile-time DI auto-registration — [Scoped]/[Singleton]/[Transient] generates IServiceCollection registration code. |
| AutoMap.Generator | Compile-time object mapping with generated extension methods. AOT-safe AutoMapper alternative. |
| AutoValidate.Generator | Compile-time FluentValidation wiring — discovers validators and generates AddValidators(). |
| AutoResult.Generator | Compile-time Result<T> — [TryWrap] generates Try*() wrappers for every public method. |
| AutoQuery.Generator | Compile-time LINQ query specs — [QuerySpec] generates a strongly-typed Apply(IQueryable<T>). |
| Package | Downloads | Description |
|---|---|---|
| AutoWire | Compile-time dependency injection auto-registration for | |
| AutoMap.Generator | Compile-time object mapping for | |
| AutoQuery.Generator | Compile-time query composition for IQueryable using Roslyn incremental source generators | |
| AutoArchitecture | Compile-time architecture/dependency-rule enforcement for | |
| AutoDispatch.Generator | Compile-time CQRS dispatcher for | |
| AutoLog.Generator | Compile-time high-performance logging for | |
| AutoValidate.Generator | Compile-time FluentValidation wiring for |
MIT