A next-generation, attribute-driven REST client framework for .NET designed for enterprise-grade resiliency, observability, multi-serialization, caching, and fault tolerance. Provides Refit-style interfaces with powerful enhancements such as Polly-based resilience policies, OpenTelemetry integration, interceptor pipeline, typed fallbacks, dynamic content negotiation, and unified DI registration. Ideal for building robust API SDKs, microservice clients, and production-grade integrations.
License
—
Deps
22
Install Size
—
Vulns
✓ 0
Published
Dec 1, 2025
$ dotnet add package Shaunebu.Common.RESTClient
Shaunebu.RESTClient is a next-generation, attribute-driven REST client framework for .NET — inspired by the simplicity of Refit, but engineered from the ground up to solve the challenges of real-world enterprise environments. While Refit focuses on mapping C# interfaces to REST endpoints, RESTClient goes beyond that philosophy:
Modern distributed systems require more than just making HTTP calls. They demand:
automatic retries, timeouts, and circuit breakers
telemetry for tracing and performance monitoring
multi-format serialization (JSON, XML, MessagePack, Protobuf)
typed fallbacks for offline or degraded operation
caching, logging, and security layers
pluggable interceptors
seamless DI integration
progress reporting for long-running uploads/downloads
RESTClient delivers all of this out-of-the-box, with zero extra setup. It is fully declarative, fully extensible, and completely aligned with how .NET developers already work.
RESTClient dynamically generates a strongly typed REST client at runtime based on an interface decorated with attributes.
Internally, the pipeline looks like this:
Interface
→ Attribute Parser
→ Proxy Builder
→ Interceptor Pipeline
→ Serialization & Content Negotiation
→ Resilience Engine (Polly)
→ HttpClient Transport
→ Response Deserialization
Each part of this pipeline is extensible, customizable, and replaceable. This architecture allows RESTClient to provide:
per-method resilience
automatic serialization based on headers
observability hooks
request/response interceptors
fallback logic
caching
runtime validation
total control over HTTP behavior
with no boilerplate.
RESTClient is built on a clear philosophy:
Developers define behavior through attributes — not configuration files or boilerplate.
Retries, timeouts, logging, diagnostics, and telemetry are enabled automatically.
Interceptors, serializers, URL formatters, fallback providers — all pluggable.
Built-in OpenTelemetry tracing, metrics, and structured logging support.
Typed fallbacks and caching allow clients to operate gracefully under failure.
JSON, XML, MessagePack, Protobuf, FormUrlEncoded, Multipart.
A single AddRESTClient<T>() registers everything needed.
Refit provides a great abstraction for defining REST APIs as interfaces.
Shaunebu.RESTClient takes this philosophy and extends it into a complete, production-ready SDK framework.
Accept headers.dotnet add package Shaunebu.Common.RESTClient
public interface IPostsApi
{
[Get("/posts")]
Task<List<Post>> GetPostsAsync();
[Post("/posts")]
Task<Post> CreatePostAsync([Body] CreatePostRequest request);
}
// Dependency Injection
services.AddRESTClient<IPostsApi>("https://jsonplaceholder.typicode.com");
// Usage
var api = provider.GetRequiredService<IPostsApi>();
var result = await api.CreatePostAsync(new CreatePostRequest
{
Title = "Hello World",
Body = "RESTClient is amazing!"
});
✅ No configuration needed — serializers, resilience policies, interceptors, and handlers are automatically registered.
All global settings are managed through RESTClientOptions:
services.AddRESTClient(options =>
{
options.Timeout.RequestTimeoutSeconds = 30;
options.Resilience.RetryCount = 3;
options.Resilience.CircuitBreakerThreshold = 5;
options.DefaultCollectionFormat = CollectionFormat.Csv;
options.BufferResponse = true;
});
appsettings.json registration{
"RestClients": [
{
"Interface": "MyApp.Api.IPostsApi, MyApp",
"BaseUrl": "https://jsonplaceholder.typicode.com"
}
]
}
Then:
services.AddRESTClientsFromConfig(Configuration);
| Attribute | Purpose |
|---|---|
[Get], [Post], [Put], [Delete], [Patch], [Head] | Declares HTTP methods |
[Body(Method = ...)] | Selects serialization format (JSON, XML, MsgPack, etc.) |
[Query(Format=..., CollectionFormat=...)] | Controls query parameter encoding |
[Host("https://...")] | Overrides the default host per method |
[Fallback(typeof(MyFallback))] | Defines a typed fallback provider |
[Retry], [Timeout], [CircuitBreaker], [Bulkhead] | Method-level resilience controls |
[Headers], [Header], [AliasAs] | Header customization |
[Multipart(Boundary="...")] | Full multipart upload control |
[RateLimit], [HealthCheck] | Advanced traffic and availability controls |
Shaunebu.RESTClient can serialize and deserialize payloads dynamically based on the Accept or Content-Type headers.
[Post("/users")]
Task<User> AddUser([Body(Method = BodySerializationMethod.Json)] User user);
[Post("/files/upload")]
[Multipart(Boundary = "----CustomBoundary")]
Task<ApiResponse<string>> UploadFile([AliasAs("file")] StreamPart file);
| Format | MIME Type | Implementation |
|---|---|---|
| JSON | application/json | SystemTextJsonSerializer / NewtonsoftJsonSerializer |
| XML | application/xml | XmlContentSerializer |
| MessagePack | application/x-msgpack | MessagePackSerializer |
| Protobuf | application/x-protobuf | ProtobufSerializer |
| FormUrlEncoded | application/x-www-form-urlencoded | UrlEncodedContentSerializer |
You can register your own serializers:
services.TryAddSingleton<IContentSerializer, MyCustomSerializer>();
The ContentNegotiator automatically chooses the correct serializer based on the request’s Accept header.
Example:
[Get("/data")]
[Headers("Accept: application/x-protobuf")]
Task<MyResponse> GetDataInProtobuf();
The library will automatically:
use ProtobufSerializer to serialize/deserialize the body,
and send the correct Content-Type.
Built-in attribute-level resilience, powered by Polly:
[Get("/data")]
[Retry(3, 2)] // Retries with exponential backoff
[Timeout(5)] // Timeout after 5 seconds
[CircuitBreaker(3, 10)] // Breaks circuit after 3 errors for 10s
Task<DataResponse> GetDataAsync();
No need to register Polly handlers manually — everything is wired automatically through DI.
If a request fails, the fallback provider can return a typed result instead of throwing an exception.
[Get("/users/{id}")]
[Fallback(typeof(UserFallbackProvider))]
Task<User> GetUserAsync(int id);
public class UserFallbackProvider
{
public Task<User> GetFallbackAsync(Exception ex) =>
Task.FromResult(new User { Id = -1, Name = "Offline User" });
}
When triggered, the response includes a header:
X-Fallback-Executed: true
Real-time upload and download progress tracking using IProgress<double>:
[Post("/upload")]
Task<ApiResponse> UploadLargeFileAsync([Body] StreamPart file, IProgress<double> progress);
The progress value reports 0.0 → 1.0 (0% → 100%).
Interceptors allow you to hook into the entire request/response lifecycle.
public class MyLoggingInterceptor : IRESTInterceptor
{
public Task OnRequestAsync(RequestContext ctx)
{
Console.WriteLine($"➡️ {ctx.Request.Method} {ctx.Request.RequestUri}");
return Task.CompletedTask;
}
public Task OnResponseAsync(ResponseContext ctx)
{
Console.WriteLine($"⬅️ {ctx.Response.StatusCode}");
return Task.CompletedTask;
}
}
Add via DI:
services.TryAddSingleton<IRESTInterceptor, MyLoggingInterceptor>();
🔐 SecurityInterceptor
🧱 ResilienceInterceptor
🪣 CachingInterceptor
🧭 OpenTelemetryInterceptor
🪵 LoggingInterceptor
🧪 DebugInterceptor
💥 FallbackInterceptor
⚙️ PollyDebugInterceptor
📊 MetricsInterceptor
Out of the box support for:
ICacheStore (default: MemoryCacheStore)
CachingInterceptor (automatic response caching)
OpenTelemetry tracing (OpenTelemetryInterceptor)
Metrics gathering (MetricsInterceptor)
| Feature | Shaunebu RESTClient | Refit |
|---|---|---|
Unified DI Registration (AddRESTClient) | ✅ | ❌ |
Centralized Config (RESTClientOptions) | ✅ | ⚠️ |
| Content Negotiation | ✅ | ❌ |
| Multiple Serializers | ✅ | ⚠️ |
| Newtonsoft + System.Text.Json | ✅ | ✅ |
| Per-method Serializer | ✅ | ❌ |
| Accept Header Auto-Detection | ✅ | ❌ |
| Multipart Boundary Control | ✅ | ⚠️ |
| Upload/Download Progress | ✅ | ❌ |
[Retry], [Timeout], [CircuitBreaker] | ✅ | ❌ |
| Typed Fallbacks | ✅ | ❌ |
| Polly Integration (Automatic) | ✅ | ⚠️ |
| RateLimit / Bulkhead / HealthCheck | ✅ | ❌ |
| Interceptors (Pipeline) | ✅ | ❌ |
| Built-in Logging & Telemetry | ✅ | ⚠️ |
| Response Caching | ✅ | ❌ |
| AppSettings Config Integration | ✅ | ❌ |
Query Formatting (CollectionFormat) | ✅ | ⚠️ |
[Host] Attribute | ✅ | ❌ |
IApiResponse<T> Support | ✅ | ✅ |
Content-Type Aware Deserialization | ✅ | ❌ |
NoThrowPolicy for graceful error handling | ✅ | ❌ |
| Source Generator & Proxy Fallback | ✅ | ✅ |
| Production-Ready Stack | ✅ | ❌ |
While Refit is excellent for lightweight API calls, it has limitations in enterprise scenarios:
Simple REST wrappers
Mobile apps
Low-infrastructure projects
Rapid prototypes
Microservices
Enterprise APIs
SDK development
Mission-critical integrations
Highly observable systems
Environments where resiliency is mandatory
In short:
Refit helps you call APIs — RESTClient helps you build SDKs for APIs.
Implement IContentSerializer to plug in any serialization logic.
Add your own interceptor for metrics, logging, or security checks.
Attach to [Fallback(typeof(...))] for custom recovery logic.
Implement IUrlParameterFormatter to change query escaping globally.
[Headers("Authorization: Bearer")]
[Host("https://api.override.com")]
public interface IComplexApi
{
[Get("/users/{id}")]
[Fallback(typeof(UserFallback))]
[Retry(3, 1)]
[Timeout(10)]
Task<User> GetUserAsync(int id);
[Post("/upload")]
[Multipart(Boundary = "----ShaunebuBoundary")]
Task<ApiResponse> UploadFileAsync([AliasAs("file")] StreamPart file, IProgress<double> progress);
}
🧱 SDK Generation for enterprise APIs
☁️ Microservice Clients with per-method resiliency
🧩 Highly observable integrations (with OpenTelemetry)
📊 Data ingestion services requiring metrics and caching
💾 Offline-capable APIs using fallbacks and memory cache
Originally inspired by Refit, but re-engineered by Jorge Perales Díaz
for real-world, large-scale systems that need reliability, observability, and full-stack resilience.
| 🚀 Feature | 🧩 Status | 🎯 Target Version | 📅 ETA | 📘 Notes |
|---|---|---|---|---|
| ✅ Core Framework Stabilization | 🟢 Completed | v1.0.0 | Q4 2024 | Core system finalized — DI registration, interceptors, serializers, Polly, caching & fallbacks. |
| ✅ Multi-Serialization Stack (JSON / XML / MessagePack / Protobuf) | 🟢 Completed | v1.0.0 | Q4 2024 | Content negotiation via headers, per-method [Body] serializer selection. |
| ⚙️ Resilience Engine v2 | 🟡 In Progress | v1.1.0 | Feb 2025 | Policy registry, adaptive retry & dynamic backoff support. |
| 🔐 Token Provider & Auth Interceptor | 🟡 In Progress | v1.1.0 | Apr 2025 | OAuth / JWT auto-refresh through ITokenProvider. |
| 💾 Caching Enhancements v2 | 🟡 In Progress | v1.1.0 | May 2025 | Redis / SQLite cache providers and configurable policies. |
| 🧠 RestClientMonitor (Dashboard & Telemetry) | 🔵 Planned | v1.2.0 | Dec 2025 | 🧩 Embedded monitor endpoint (/_restclient/monitor) showing latency, retries, cache hits & failures. |
| 🪵 Structured Logging & Audit Trail | 🔵 Planned | v1.2.0 | Dec 2025 | JSON log formatter + PII masking for GDPR compliance. |
| 🧭 OpenTelemetry Enricher Hooks | 🔵 Planned | v1.2.0 | Jun 2025 | Custom span tags & metrics from RESTClient context. |
| ⚙️ Dynamic Interceptor Pipeline | 🔵 Planned | v1.3.0 | Aug 2025 | Add / remove interceptors dynamically at runtime. |
| 🧪 In-Memory Mock Transport | 🔵 Planned | v1.3.0 | Sep 2025 | Offline transport for testing (FakeHttpHandler). |
| 🪶 Source Generator Integration | 🔵 Planned | v1.4.0 | Nov 2025 | Build-time proxy generator via incremental Roslyn. |
| 📊 CLI Tool for OpenAPI Import | 🔵 Planned | v1.4.0 | Dec 2025 | dotnet restclient import openapi.json — automatic interface generation. |
| 🧾 RESTClient Analyzer Package | 🔵 Planned | v1.4.0 | Dec 2025 | Roslyn analyzers warning about missing attributes / timeouts. |
| 🧱 SignalR Integration | 🔵 Planned | v1.5.0 | Q1 2026 | Bridge REST ↔ SignalR with automatic message routing. |
MIT © Shaunebu 2025