SyntaxCircus.Http.Resilience
0.1.4
See the version list below for details.
dotnet add package SyntaxCircus.Http.Resilience --version 0.1.4
NuGet\Install-Package SyntaxCircus.Http.Resilience -Version 0.1.4
<PackageReference Include="SyntaxCircus.Http.Resilience" Version="0.1.4" />
<PackageVersion Include="SyntaxCircus.Http.Resilience" Version="0.1.4" />
<PackageReference Include="SyntaxCircus.Http.Resilience" />
paket add SyntaxCircus.Http.Resilience --version 0.1.4
#r "nuget: SyntaxCircus.Http.Resilience, 0.1.4"
#:package SyntaxCircus.Http.Resilience@0.1.4
#addin nuget:?package=SyntaxCircus.Http.Resilience&version=0.1.4
#tool nuget:?package=SyntaxCircus.Http.Resilience&version=0.1.4
SyntaxCircus.Http.Resilience
A typed API client base, a Polly-based resilient HttpClient registration helper, and a generic cached-token provider — the pieces that keep getting rewritten every time a product calls another API.
No support guaranteed. Published as-is and maintained on a best-effort basis. Issues and PRs are welcome, but there's no SLA — fork it or vendor what you need if that's not enough.
ApiClientBase
public sealed class WidgetApiClient(HttpClient httpClient) : ApiClientBase(httpClient)
{
public Task<Widget?> GetWidgetAsync(string id, CancellationToken ct) => GetAsync<Widget>($"widgets/{id}", ct);
public Task CreateWidgetAsync(Widget widget, CancellationToken ct) => PostAsync("widgets", widget, ct);
}
JSON GetAsync/GetWithETagAsync (conditional GET with a per-URL ETag cache)/PostAsync/PutAsync (with If-Match from the cached ETag)/DeleteAsync, and centralized error handling: a non-success response is translated into a ProblemDetailsException (StatusCode, Type, Title, Errors) when the body is an RFC 7807 ProblemDetails payload. Bearer-token attachment is left to the caller — register a DelegatingHandler on the typed client via AddHttpMessageHandler<T>() rather than baking auth into the base class.
GetWithETagAsync takes an optional useConditionalRequest parameter (default true, preserving the conditional-GET/304 behavior above). Pass false for a "load for edit" call that should always return a fresh body — no If-None-Match is sent and a 304 can never happen, but the response's ETag is still cached for a subsequent PutAsync/DeleteAsync on the same URL. Useful for long-lived typed-client instances (e.g. one per web-app session/circuit) where a caller reads the same URL more than once and always wants the current value, not a cached-away 304.
Need to send something the JSON verb helpers don't fit — multipart form content, a binary download, custom headers? Use SendAsync(HttpRequestMessage, ct) / ReadJsonAsync<T>(HttpResponseMessage, ct), protected members that run the same OnResponseReceivedAsync hook, ProblemDetails translation, and ETag caching as the verb helpers, while leaving you in control of the request/response shape:
public async Task<Widget> UploadAsync(Stream file, CancellationToken ct)
{
using var request = new HttpRequestMessage(HttpMethod.Post, "widgets/upload") { Content = new StreamContent(file) };
using var response = await SendAsync(request, ct);
return (await ReadJsonAsync<Widget>(response, ct))!;
}
AddResilientHttpClient
builder.Services.AddResilientHttpClient(
"widgets-api",
client => client.BaseAddress = new Uri("https://widgets.example.com"),
retryCount: 3)
.AddTypedClient<WidgetApiClient>();
Wraps the named HttpClient in a Polly retry (exponential backoff + jitter) and circuit-breaker pipeline, retrying transient errors and 429/5xx. Pass aiMode: true for AI/LLM provider clients where a 429 means "back off on purpose" rather than "something's broken" — it's excluded from retry/circuit-breaking in that mode.
CachedTokenProvider
var tokenProvider = new CachedTokenProvider(async ct =>
{
var token = await FetchClientCredentialsTokenAsync(ct);
return new CachedToken(token.AccessToken, DateTimeOffset.UtcNow.AddSeconds(token.ExpiresIn));
});
var accessToken = await tokenProvider.GetTokenAsync();
A semaphore-guarded token cache, refreshed under lock once it's within expirySkew (default 60s) of expiry. The acquisition delegate is entirely up to you — client-credentials grant, a custom token endpoint, whatever your worker-to-API auth needs.
Contributing
Issues and pull requests are welcome:
- Keep changes focused, with a clear description of the behavior change.
- Match the existing code style (see
.editorconfig). - Call out any breaking changes to the public API in your PR description.
License
MIT — see LICENSE.txt.
| 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
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Http.Resilience (>= 9.9.0)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on SyntaxCircus.Http.Resilience:
| Package | Downloads |
|---|---|
|
SyntaxCircus.Cmsify.Client
Typed .NET client for connecting to and managing the Cmsify headless CMS API. |
|
|
SyntaxCircus.Cmsify.Client.DistributedCaching
Provider-neutral IDistributedCache add-on for the SyntaxCircus Cmsify .NET client. |
|
|
SyntaxCircus.AI.Providers
Low-level typed HTTP clients for the Anthropic Messages API and the Gemini generateContent API: request/response DTOs, rate-limit handling, and Retry-After parsing. Not a unified provider abstraction — just the plumbing both APIs otherwise get reimplemented for. |
|
|
SyntaxCircus.Cmsify.Components
Reusable, restylable Blazor components for editing and managing Cmsify content: field editors, a composed content edit form, and a content list view, with optional SDK-backed smart wrappers. |
GitHub repositories
This package is not used by any popular GitHub repositories.