Dosaic.Extensions.RestEase
1.2.49
dotnet add package Dosaic.Extensions.RestEase --version 1.2.49
NuGet\Install-Package Dosaic.Extensions.RestEase -Version 1.2.49
<PackageReference Include="Dosaic.Extensions.RestEase" Version="1.2.49" />
<PackageVersion Include="Dosaic.Extensions.RestEase" Version="1.2.49" />
<PackageReference Include="Dosaic.Extensions.RestEase" />
paket add Dosaic.Extensions.RestEase --version 1.2.49
#r "nuget: Dosaic.Extensions.RestEase, 1.2.49"
#:package Dosaic.Extensions.RestEase@1.2.49
#addin nuget:?package=Dosaic.Extensions.RestEase&version=1.2.49
#tool nuget:?package=Dosaic.Extensions.RestEase&version=1.2.49
Dosaic.Extensions.RestEase
Dosaic.Extensions.RestEase builds typed HTTP API clients on top of RestEase. It plugs into IHttpClientFactory, uses System.Text.Json, supports a composable DelegatingHandler middleware chain, Polly v8 resilience pipelines (via Microsoft.Extensions.Http.Resilience), response caching via IDistributedCache (in-memory or Redis), and ships an OAuth2 integration with thread-safe token caching, automatic refresh-token rotation, and 401-triggered force-refresh retry.
Installation
dotnet add package Dosaic.Extensions.RestEase
<PackageReference Include="Dosaic.Extensions.RestEase" Version="" />
Features
- Typed HTTP clients — define an interface with RestEase attributes, get a fully wired client back.
- IHttpClientFactory integration — proper socket pooling, DNS refresh, named-client lifetime management.
- DelegatingHandler middleware chain — plug in correlation IDs, logging, custom auth, rate-limit headers, request signing, etc.
- Polly v8 resilience pipelines — retry with jitter + exponential backoff,
Retry-Afterhonouring, timeouts, circuit breaker, hedging, bulkhead. Powered byMicrosoft.Extensions.Http.Resilience. - Response caching — Polly v8 strategy backed by
IDistributedCache. In-memory by default, swap to Redis / SQL Server / NCache with one line. HonoursCache-Control(max-age,no-store,no-cache,private). - Client-side rate limiting — Polly v8 strategies (SlidingWindow / FixedWindow / TokenBucket / Concurrency) backed by
System.Threading.RateLimiting. ThrowsRateLimiterRejectedExceptionon rejection. - Single Polly pipeline — caching + retry + rate limit + timeout all composed into one
ResiliencePipeline<HttpResponseMessage>mounted viaAddResilienceHandler. Auto-wired fromCaching/Resilience/RateLimitsconfig blocks. Custom strategies viaAddPolly(Action<...>). - OAuth2 out of the box —
ClientCredentialsandPasswordgrants; transparent refresh-token rotation; concurrent-call coalescing viaSemaphoreSlim; 401-triggered forced refresh + retry. - Pluggable
ITokenProvider— swap in a distributed token cache, mTLS, API key, or any custom auth strategy. - System.Text.Json only — override
JsonSerializerOptionsper-client; sane web defaults out of the box. - DI builder fluent API —
AddRestEaseApi<TApi>()returnsIRestEaseClientBuilderfor composition. - Static factory for non-DI usage —
RestClientFactory.Create<TApi>(...)still available.
Quick Start (DI — recommended)
using Dosaic.Extensions.RestEase.DependencyInjection;
services.AddRestEaseApi<IUserApi>(o => o.BaseAddress = "https://api.example.com")
.AddResilience();
Resolve the typed client:
public class UserService(IUserApi api)
{
public Task<User> Get(Guid id, CancellationToken ct) => api.GetUserAsync(id, ct);
}
Without the Options pattern
Pass a RestEaseClientOptions instance directly — no lambda, no IConfiguration binding:
var options = new RestEaseClientOptions
{
BaseAddress = "https://api.example.com",
Timeout = TimeSpan.FromSeconds(30),
UserAgent = "my-service/1.0"
};
options.DefaultHeaders["X-Tenant"] = tenantId;
services.AddRestEaseApi<IUserApi>(options);
Overload signatures:
AddRestEaseApi<TApi>(this IServiceCollection, RestEaseClientOptions);
AddRestEaseApi<TApi>(this IServiceCollection, string name, RestEaseClientOptions);
The instance is copied into the named-options store at registration time. IOptionsMonitor<RestEaseClientOptions> is still wired internally (handlers depend on it) — caller does not touch it.
Interface Definition
using RestEase;
public interface IUserApi
{
[Get("users/{id}")]
Task<User> GetUserAsync([Path] Guid id, CancellationToken ct);
[Post("users")]
Task<User> CreateAsync([Body] User user, CancellationToken ct);
[Put("users/{id}")]
Task UpdateAsync([Path] Guid id, [Body] User user, CancellationToken ct);
[Delete("users/{id}")]
Task DeleteAsync([Path] Guid id, CancellationToken ct);
}
Configuration Binding
MyApi:
BaseAddress: https://api.example.com
Timeout: 00:00:30
UserAgent: my-service/1.0
Authentication:
Enabled: true
BaseUrl: https://auth.example.com
TokenUrlPath: /realms/my-realm/protocol/openid-connect/token
GrantType: ClientCredentials
ClientId: my-client
ClientSecret: s3cr3t
Scope: api.read api.write
RefreshSkew: 00:00:30
Caching:
Enabled: true
DefaultTtl: 00:05:00
MaxTtl: 00:30:00
RespectCacheControl: true
IncludeAuthorizationInKey: false
KeyPrefix: "myapi:"
Methods: [GET, HEAD]
CacheableStatusCodes: [200, 404]
Resilience:
Enabled: true
MaxRetryAttempts: 3
BaseDelay: 00:00:00.200
AttemptTimeout: 00:00:10
TotalRequestTimeout: 00:00:30
RateLimits:
Enabled: true
ThrowOnRejection: false # false = return 429 + Retry-After, true = throw HttpRequestException
Distributed: false # true = one budget for all processes, see "Distributed rate limits"
DistributedKey: partner-api # identifies the shared budget; default = client name
SlidingWindow:
Enabled: true
PermitLimit: 50 # max requests per window
Window: 00:00:10 # window size
SegmentsPerWindow: 4 # window slices for smoothing
AutoReplenishment: true
QueueProcessingOrder: OldestFirst # OldestFirst | NewestFirst
QueueLimit: 1024
FixedWindow:
Enabled: false
PermitLimit: 100
Window: 00:00:01
AutoReplenishment: true
QueueLimit: 0
TokenBucket:
Enabled: false
PermitLimit: 100 # token bucket capacity
TokensPerPeriod: 10
ReplenishmentPeriod: 00:00:01
AutoReplenishment: true
QueueLimit: 0
Concurrency:
Enabled: true
PermitLimit: 10 # max in-flight requests
QueueLimit: 1024
Multiple limiters enabled simultaneously stack as Polly strategies in single pipeline. Order: Cache → SlidingWindow → FixedWindow → TokenBucket → Concurrency → TotalTimeout → Retry → AttemptTimeout (outermost-first).
services.AddRestEaseApiFromConfiguration<IUserApi>(configuration, "MyApi");
// single Polly pipeline auto-wired with cache + limiters + retry + timeouts
Extra Polly strategies via .AddPolly((pb, ctx) => pb.AddHedging(...)) — stacks alongside auto-wired pipeline.
Overload signatures:
AddRestEaseApiFromConfiguration<TApi>(IServiceCollection, IConfiguration, string sectionKey);
AddRestEaseApiFromConfiguration<TApi>(IServiceCollection, string name, IConfiguration, string sectionKey);
AddRestEaseApiFromConfiguration<TApi>(IServiceCollection, IConfigurationSection);
AddRestEaseApiFromConfiguration<TApi>(IServiceCollection, string name, IConfigurationSection);
Distributed rate limits
By default every limiter lives in the process that created it: three replicas with PermitLimit: 50 send up to 150 requests per window. Set RateLimits:Distributed: true to let all replicas draw from one budget.
| Key | Type | Default | Description |
|---|---|---|---|
<section>:RateLimits:Distributed |
bool |
false |
false keeps the in-process System.Threading.RateLimiting limiters. true creates every enabled limiter through the registered IDistributedRateLimiterFactory. |
<section>:RateLimits:DistributedKey |
string |
client name | Identifies the shared budget. Clients (in any process) with the same key share it. The client name defaults to the full type name of the API interface. |
PartnerApi:
BaseAddress: https://partner.example.com
RateLimits:
Distributed: true
DistributedKey: partner-api
SlidingWindow:
PermitLimit: 50
Window: 00:00:10
SegmentsPerWindow: 4
QueueLimit: 100
IDistributedRateLimiterFactory is not implemented by this package. It comes from:
Dosaic.Plugins.Caching.Redis— registers a Redis backed factory (redisCache:useInMemory: false) or an in-process one (useInMemory: true). Nothing else to wire up.InProcessRateLimiterFactory(Dosaic.Extensions.Abstractions) — shares a budget per key inside one process; register it yourself for tests or single instance services:services.AddSingleton<IDistributedRateLimiterFactory, InProcessRateLimiterFactory>().- Your own implementation.
If Distributed is true and no factory is registered, building the pipeline — the first request of the client — throws an InvalidOperationException.
Semantics that differ from the in-process limiters are a property of the factory. For the Redis factory (waiting by polling, no queue order across processes, lease TTL of the concurrency limiter, fallback when Redis is down) see the Redis plugin README.
Where the abstraction lives:
IDistributedRateLimiterFactoryis defined inDosaic.Extensions.Abstractions(namespaceDosaic.Extensions.Abstractions.RateLimiting), which this package now references.Dosaic.Hosting.Abstractionswas the alternative, but it carries theMicrosoft.AspNetCore.Appframework reference plus YamlDotNet, Vogen and Chronos, andDosaic.Extensions.RestEaseis meant to stay usable from console apps and workers without the Dosaic host.Dosaic.Extensions.Abstractionsonly addsSystem.Threading.RateLimiting, which RestEase already pulled in through Polly.
DI Builder API
AddRestEaseApi<TApi>() returns an IRestEaseClientBuilder:
| Method | Purpose |
|---|---|
.ConfigureOptions(Action<RestEaseClientOptions>) |
Mutate client options |
.ConfigureJson(Action<JsonSerializerOptions>) |
Tweak the System.Text.Json options |
.ConfigureHttpClient(Action<HttpClient>) |
Raw HttpClient configuration |
.AddOAuth2(Action<AuthenticationConfig>) |
Enable the built-in OAuth2 token provider |
.AddTokenProvider<T>() |
Plug in a custom ITokenProvider (registered in DI) |
.AddHandler<THandler>() |
Insert a DelegatingHandler into the chain (outside Polly pipeline) |
.AddResilience(Action<ResilienceConfig>?) |
Enable + tune retry / timeouts |
.AddCaching(Action<HttpCacheOptions>?) |
Enable + tune response cache (IDistributedCache-backed Polly strategy) |
.AddRateLimits(Action<RateLimitsConfig>?) |
Enable + tune rate limiters (SlidingWindow / FixedWindow / TokenBucket / Concurrency) |
.AddPolly(Action<ResiliencePipelineBuilder<HttpResponseMessage>, ResilienceHandlerContext>) |
Mount additional Polly v8 pipeline (separate slot, stacks with auto-pipeline) |
OAuth2
Client Credentials
services.AddRestEaseApi<IUserApi>(o => o.BaseAddress = "https://api.example.com")
.AddOAuth2(a =>
{
a.BaseUrl = "https://auth.example.com";
a.TokenUrlPath = "/oauth/token";
a.GrantType = GrantType.ClientCredentials;
a.ClientId = "my-client";
a.ClientSecret = "s3cr3t";
a.Scope = "api.read";
})
.AddResilience();
Resource Owner Password
.AddOAuth2(a =>
{
a.BaseUrl = "https://auth.example.com";
a.TokenUrlPath = "/oauth/token";
a.GrantType = GrantType.Password;
a.ClientId = "my-client";
a.Username = "alice";
a.Password = "s3cr3t";
});
Authorization Code grant is not supported. The browser leg (user redirect + consent) is out of scope for a server-side HTTP client. Use ASP.NET OIDC (
AddOpenIdConnect) to handle the interactive login, then plug a customITokenProviderthat readsHttpContext.GetTokenAsync("access_token")and feeds it into this client.
How the OAuth2 pipeline behaves
- Concurrent-call coalescing. Multiple requests during a token refresh hit the IdP once — gated by
SemaphoreSlimwith double-checked locking. - Refresh-token rotation. When the access token expires but the refresh token is still valid, a
refresh_tokengrant is used automatically. - Clock-skew buffer.
AuthenticationConfig.RefreshSkew(default 30s) refreshes the token slightly before its real expiry. - 401 → force-refresh + retry. If the server returns 401 with our auto-injected token, the cached token is invalidated, a fresh one is fetched, and the request is retried once.
- User-supplied
Authorizationheader is respected — the handler never overwrites a header the caller already set.
Custom ITokenProvider
For distributed token caches, mTLS, API key, or anything else:
public sealed class RedisTokenProvider(IConnectionMultiplexer redis) : ITokenProvider
{
public async Task<AccessToken> GetTokenAsync(bool forceRefresh, CancellationToken ct) { /* ... */ }
public void Invalidate() { /* ... */ }
}
services.AddSingleton<RedisTokenProvider>();
services.AddRestEaseApi<IUserApi>(o => o.BaseAddress = "...")
.AddTokenProvider<RedisTokenProvider>();
Resilience
Built on Polly v8 + Microsoft.Extensions.Http.Resilience. Three equivalent ways to enable:
Builder method (most concise):
services.AddRestEaseApi<IUserApi>(o => o.BaseAddress = "https://api.example.com")
.AddResilience(r =>
{
r.MaxRetryAttempts = 3;
r.BaseDelay = TimeSpan.FromMilliseconds(200);
r.AttemptTimeout = TimeSpan.FromSeconds(10);
r.TotalRequestTimeout = TimeSpan.FromSeconds(30);
});
Inline options:
services.AddRestEaseApi<IUserApi>(o =>
{
o.BaseAddress = "https://api.example.com";
o.Resilience = new ResilienceConfig { Enabled = true, MaxRetryAttempts = 3 };
});
Config-driven: see Configuration Binding section.
Defaults: exponential backoff + jitter, HttpRetryStrategyOptions predicate (5xx + 408 + 429 + HttpRequestException, honours Retry-After).
ResilienceConfig
| Property | Type | Description |
|---|---|---|
Enabled |
bool |
Master switch (AddResilience() sets to true) |
MaxRetryAttempts |
int? |
Defaults to 3 |
BaseDelay |
TimeSpan? |
Defaults to 500ms |
AttemptTimeout |
TimeSpan? |
Per-try timeout |
TotalRequestTimeout |
TimeSpan? |
Outermost timeout across retries |
AdditionalRetryStatusCodes |
HashSet<HttpStatusCode> |
Extra retryable codes appended to default predicate |
ConfigureRetry |
Action<HttpRetryStrategyOptions>? |
Last-mile mutation of retry strategy (backoff, jitter, custom ShouldHandle, OnRetry hooks) |
Advanced retry tuning
.AddResilience(r =>
{
r.MaxRetryAttempts = 5;
r.AdditionalRetryStatusCodes.Add(HttpStatusCode.BadGateway);
r.ConfigureRetry = retry =>
{
retry.BackoffType = DelayBackoffType.Exponential;
retry.UseJitter = true;
retry.MaxDelay = TimeSpan.FromSeconds(30);
retry.OnRetry = args => { /* log */ return default; };
};
});
Custom Polly pipeline (hedging / circuit breaker / etc.)
services.AddRestEaseApi<IUserApi>(o => o.BaseAddress = "...")
.AddPolly((pb, ctx) =>
{
pb.AddHedging(new HedgingStrategyOptions<HttpResponseMessage> { MaxHedgedAttempts = 2 });
pb.AddCircuitBreaker(new CircuitBreakerStrategyOptions<HttpResponseMessage> { FailureRatio = 0.1 });
});
AddPolly mounts an additional resilience handler (separate slot — multiple calls produce stacked pipelines). Auto-pipeline (from AddResilience / AddCaching / AddRateLimits) always runs alongside.
Static factory (non-DI)
RestClientFactory.Create<T> uses Polly directly via ResiliencePipeline<HttpResponseMessage> — defaults from RestEaseDefaults.CreateDefaultPipeline() (3 retries exp + jitter, 100s overall timeout).
Middleware (DelegatingHandler chain)
public sealed class CorrelationIdHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
{
request.Headers.TryAddWithoutValidation("X-Correlation-Id", Activity.Current?.TraceId.ToString() ?? Guid.NewGuid().ToString("N"));
return base.SendAsync(request, ct);
}
}
services.AddRestEaseApi<IUserApi>(o => o.BaseAddress = "...")
.AddHandler<CorrelationIdHandler>()
.AddOAuth2(a => { /* ... */ })
.AddResilience();
Custom DelegatingHandler chain wraps the Polly resilience handler at the outside. Order:
[CorrelationId] → [OAuth2] → [Polly pipeline: cache → limiters → retry → timeout] → SocketsHttpHandler
Custom handlers run on every retry (since they're outside the Polly pipeline). Put cache inside the Polly pipeline (via Caching options) if you want hits to short-circuit auth + retries.
Response Caching
Cache implemented as Polly v8 strategy (HttpCacheResilienceStrategy) backed by IDistributedCache. Same abstraction covers in-process and out-of-process stores — swap backing implementation, strategy code unchanged.
In-memory (default)
services.AddRestEaseApi<IUserApi>(o => o.BaseAddress = "https://api.example.com")
.AddCaching(c => c.DefaultTtl = TimeSpan.FromMinutes(10));
If no IDistributedCache is already registered, AddDistributedMemoryCache() is auto-registered (per-process, no shared state).
Redis (shared across replicas)
services.AddStackExchangeRedisCache(o =>
{
o.Configuration = "redis:6379";
o.InstanceName = "my-service:";
});
services.AddRestEaseApi<IUserApi>(o => o.BaseAddress = "https://api.example.com")
.AddCaching(c => c.DefaultTtl = TimeSpan.FromMinutes(10));
Any IDistributedCache implementation works: Microsoft.Extensions.Caching.StackExchangeRedis, Microsoft.Extensions.Caching.SqlServer, NCache, etc. Register before AddRestEaseApi<T> so the cache strategy resolves your impl instead of the in-memory fallback.
HttpCacheOptions
| Property | Type | Default | Description |
|---|---|---|---|
Enabled |
bool |
true |
Master switch |
DefaultTtl |
TimeSpan |
5 min |
TTL when no Cache-Control: max-age on response |
MaxTtl |
TimeSpan? |
null |
Upper bound — clamps server-provided max-age |
Methods |
HashSet<string> |
{ "GET" } |
HTTP method names to cache (case-insensitive) |
CacheableStatusCodes |
HashSet<int> |
200, 203, 300, 301, 404, 410 |
Statuses eligible for storage |
RespectCacheControl |
bool |
true |
Honour no-store, no-cache, private, max-age |
IncludeAuthorizationInKey |
bool |
false |
When true, SHA256-hashed Authorization header gets folded into key |
KeyPrefix |
string |
dosaic:restease: |
Prefix prepended to every key |
KeyBuilder |
Func<HttpRequestMessage,string>? |
null |
Override default method + url + auth-hash key. Code-only — not bindable from config |
ShouldCacheRequest |
Func<HttpRequestMessage,bool>? |
null |
Per-request opt-out. Code-only |
ShouldCacheResponse |
Func<HttpResponseMessage,bool>? |
null |
Per-response opt-out. Code-only |
Config-driven caching
HttpCacheOptions lives on RestEaseClientOptions.Caching — bind from same config section:
Api:
BaseAddress: https://api.example.com
Caching:
Enabled: true
DefaultTtl: 00:10:00
KeyPrefix: "myapi:"
Methods: [GET, HEAD]
services.AddRestEaseApiFromConfiguration<IUserApi>(configuration, "Api");
// Caching block auto-wires into Polly pipeline
For code-only fields (KeyBuilder, ShouldCacheRequest, ShouldCacheResponse), layer .AddCaching(...) after config-binding:
services.AddRestEaseApiFromConfiguration<IUserApi>(configuration, "Api")
.AddCaching(c => c.KeyBuilder = req => $"v2:{req.RequestUri}");
Cache-Control semantics
- Request
Cache-Control: no-storeorno-cache→ bypass cache - Response
Cache-Control: no-store,no-cache, orprivate→ not stored - Response
Cache-Control: max-age=N→ TTL =min(N, MaxTtl ?? N) - No
Cache-Controlon response →DefaultTtl
Strategy ordering (inside Polly pipeline)
Pipeline order: Cache → RateLimiters → TotalTimeout → Retry → AttemptTimeout. Cache outermost — hit short-circuits all other strategies + the inner HTTP call. OAuth2 lives in the DelegatingHandler chain outside the Polly pipeline — cache hit skips token fetch automatically.
Key construction
Default key: KeyPrefix + "{METHOD} {ABSOLUTE-URL}".
Authorization header is excluded by default — same response served to every caller. Opt-in via IncludeAuthorizationInKey = true to isolate per-token (header value is SHA256-hashed, never stored raw).
Custom key:
.AddCaching(c => c.KeyBuilder = req =>
$"{req.Method.Method}:{req.RequestUri}:tenant={req.Headers.GetValues("X-Tenant").First()}");
Storage format
Each cache entry is a JSON envelope: status code, response headers, content headers, body bytes. Easy to inspect in Redis with GET key | jq.
Why IDistributedCache?
One abstraction covers in-process and out-of-process. IMemoryCache would lock you into a single replica. If you only need L1, AddDistributedMemoryCache is a MemoryDistributedCache — same in-memory performance, swappable later without code change. Two-tier (L1+L2) is out of scope here — use HybridCache if you need it and wire your own handler.
JSON
System.Text.Json is the only supported serializer. Defaults:
new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter() }
};
Override globally per client:
.ConfigureJson(j =>
{
j.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
j.Converters.Add(new MyCustomConverter());
});
Static Factory (non-DI)
using Dosaic.Extensions.RestEase;
// Simplest
var api = RestClientFactory.Create<IUserApi>("https://api.example.com");
// With OAuth2
var api = RestClientFactory.Create<IUserApi>("https://api.example.com", authConfig);
// With a custom resilience pipeline
var api = RestClientFactory.Create<IUserApi>("https://api.example.com", pipeline);
// Full configuration
var api = RestClientFactory.Create<IUserApi>("https://api.example.com", o =>
{
o.Timeout = TimeSpan.FromSeconds(20);
o.UserAgent = "my-service/1.0";
o.DefaultHeaders["X-Tenant"] = tenantId;
o.Authentication = authConfig;
o.ResiliencePipeline = pipeline;
o.JsonOptions = customJson;
});
The static factory builds a fresh handler chain (UserAgent → Resilience → OAuth2 → SocketsHttpHandler) per call. Prefer the DI path in long-running apps to benefit from IHttpClientFactory pooling.
Best Practices
- Use the DI path for hosted services. The static factory is for short-lived tools and tests.
- Call
.AddResilience()(or.AddCaching()/.AddRateLimits(), or enable equivalent option blocks) — auto-pipeline mounts unconditionally but adds zero strategies when no block is enabled. - One client interface per service. Don't reuse the same interface for two upstreams — register each with a distinct name.
- Set
RefreshSkew≥ 10 s so tokens refresh before they expire on the wire. Default is 30 s. - Distributed token cache: implement
ITokenProviderbacked by Redis/Vault when you run multiple replicas — otherwise each replica holds its own copy. - Don't combine
AddOAuth2with manualAuthorizationheaders on every request. If you must override per-call, setrequest.Headers.Authorization— the handler honours it. - Custom handlers stay stateless —
IHttpClientFactoryinstantiates them per request scope; per-handler state will surprise you. - Use the same client name + key everywhere —
AddRestEaseApi<TApi>(name)is keyed by name internally (options, token provider, http client all share it). - Override
JsonOptionsrather than rolling your own serializer — System.Text.Json is the only path supported.
API Reference
RestClientFactory (static)
| Method | Description |
|---|---|
Create<T>(string baseAddress) |
Default pipeline, no auth |
Create<T>(string baseAddress, AuthenticationConfig) |
Adds OAuth2 |
Create<T>(string baseAddress, ResiliencePipeline<HttpResponseMessage>) |
Replaces resilience pipeline |
Create<T>(string baseAddress, AuthenticationConfig, ResiliencePipeline<HttpResponseMessage>) |
Auth + custom pipeline |
Create<T>(string baseAddress, Action<StandaloneClientOptions>) |
Full options bag |
RestEaseDefaults
| Member | Description |
|---|---|
CreateDefaultJsonOptions() |
Web-mode STJ options w/ JsonStringEnumConverter |
CreateDefaultPipeline() |
Polly v8 retry + timeout pipeline (default for static factory) |
RestEaseClientOptions
| Property | Type | Description |
|---|---|---|
BaseAddress |
string |
API base URL |
Timeout |
TimeSpan? |
HttpClient.Timeout |
UserAgent |
string |
Appended to User-Agent header |
Authentication |
AuthenticationConfig |
OAuth2 settings |
JsonOptions |
JsonSerializerOptions |
Override STJ defaults |
DefaultHeaders |
Dictionary<string,string> |
Static request headers |
AuthenticationConfig
| Property | Type | Description |
|---|---|---|
Enabled |
bool |
Master switch |
BaseUrl |
string |
IdP base URL |
TokenUrlPath |
string |
Token endpoint path |
GrantType |
GrantType |
ClientCredentials · Password |
ClientId / ClientSecret |
string |
OAuth2 client identity |
Username / Password |
string |
Resource owner credentials |
Scope / Audience |
string |
Optional scope / audience |
RefreshSkew |
TimeSpan |
Refresh-buffer before real expiry. Default 30s |
ITokenProvider
public interface ITokenProvider
{
Task<AccessToken> GetTokenAsync(bool forceRefresh, CancellationToken cancellationToken);
void Invalidate();
}
public sealed class AccessToken
{
public string TokenType { get; init; }
public string Value { get; init; }
public DateTimeOffset ExpiresAt { get; init; }
}
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Dosaic.Extensions.Abstractions (>= 1.2.49)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.14)
- Microsoft.Extensions.Caching.Memory (>= 10.0.14)
- Microsoft.Extensions.DependencyInjection (>= 10.0.14)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.14)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.14)
- Microsoft.Extensions.Http (>= 10.0.14)
- Microsoft.Extensions.Http.Resilience (>= 10.11.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.14)
- Microsoft.Extensions.Options (>= 10.0.14)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.14)
- Newtonsoft.Json (>= 13.0.4)
- Polly (>= 8.9.0)
- RestEase (>= 1.6.4)
- System.Threading.RateLimiting (>= 10.0.14)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.2.49 | 63 | 11/11/2026 |
| 1.2.48 | 106 | 11/4/2026 |
| 1.2.47 | 100 | 11/3/2026 |
| 1.2.45 | 106 | 10/30/2026 |
| 1.2.44 | 140 | 10/14/2026 |
| 1.2.43 | 122 | 10/13/2026 |
| 1.2.42 | 116 | 9/24/2026 |
| 1.2.41 | 134 | 9/7/2026 |
| 1.2.40 | 250 | 8/27/2026 |
| 1.2.40-alpha.1 | 89 | 8/27/2026 |
| 1.2.39 | 130 | 8/25/2026 |
| 1.2.38 | 141 | 7/30/2026 |
| 1.2.37 | 147 | 7/14/2026 |
| 1.2.36 | 147 | 7/10/2026 |
| 1.2.35 | 175 | 6/19/2026 |
| 1.2.34 | 151 | 6/10/2026 |
| 1.2.33 | 143 | 6/2/2026 |
| 1.2.31 | 142 | 5/28/2026 |
| 1.2.30 | 151 | 5/7/2026 |
| 1.2.29 | 148 | 5/5/2026 |