Dosaic.Extensions.RestEase 1.2.34

dotnet add package Dosaic.Extensions.RestEase --version 1.2.34
                    
NuGet\Install-Package Dosaic.Extensions.RestEase -Version 1.2.34
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Dosaic.Extensions.RestEase" Version="1.2.34" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Dosaic.Extensions.RestEase" Version="1.2.34" />
                    
Directory.Packages.props
<PackageReference Include="Dosaic.Extensions.RestEase" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Dosaic.Extensions.RestEase --version 1.2.34
                    
#r "nuget: Dosaic.Extensions.RestEase, 1.2.34"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Dosaic.Extensions.RestEase@1.2.34
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Dosaic.Extensions.RestEase&version=1.2.34
                    
Install as a Cake Addin
#tool nuget:?package=Dosaic.Extensions.RestEase&version=1.2.34
                    
Install as a Cake Tool

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-After honouring, timeouts, circuit breaker, hedging, bulkhead. Powered by Microsoft.Extensions.Http.Resilience.
  • Response caching — Polly v8 strategy backed by IDistributedCache. In-memory by default, swap to Redis / SQL Server / NCache with one line. Honours Cache-Control (max-age, no-store, no-cache, private).
  • Client-side rate limiting — Polly v8 strategies (SlidingWindow / FixedWindow / TokenBucket / Concurrency) backed by System.Threading.RateLimiting. Throws RateLimiterRejectedException on rejection.
  • Single Polly pipeline — caching + retry + rate limit + timeout all composed into one ResiliencePipeline<HttpResponseMessage> mounted via AddResilienceHandler. Auto-wired from Caching / Resilience / RateLimits config blocks. Custom strategies via AddPolly(Action<...>).
  • OAuth2 out of the box — ClientCredentials and Password grants; transparent refresh-token rotation; concurrent-call coalescing via SemaphoreSlim; 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 JsonSerializerOptions per-client; sane web defaults out of the box.
  • DI builder fluent APIAddRestEaseApi<TApi>() returns IRestEaseClientBuilder for composition.
  • Static factory for non-DI usageRestClientFactory.Create<TApi>(...) still available.
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
    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);

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 custom ITokenProvider that reads HttpContext.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 SemaphoreSlim with double-checked locking.
  • Refresh-token rotation. When the access token expires but the refresh token is still valid, a refresh_token grant 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 Authorization header 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-store or no-cache → bypass cache
  • Response Cache-Control: no-store, no-cache, or private → not stored
  • Response Cache-Control: max-age=N → TTL = min(N, MaxTtl ?? N)
  • No Cache-Control on 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 ITokenProvider backed by Redis/Vault when you run multiple replicas — otherwise each replica holds its own copy.
  • Don't combine AddOAuth2 with manual Authorization headers on every request. If you must override per-call, set request.Headers.Authorization — the handler honours it.
  • Custom handlers stay statelessIHttpClientFactory instantiates them per request scope; per-handler state will surprise you.
  • Use the same client name + key everywhereAddRestEaseApi<TApi>(name) is keyed by name internally (options, token provider, http client all share it).
  • Override JsonOptions rather 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.34 89 6/10/2026
1.2.33 101 6/2/2026
1.2.31 102 5/28/2026
1.2.30 112 5/7/2026
1.2.29 108 5/5/2026
1.2.28 117 4/30/2026
1.2.27 102 4/29/2026
1.2.26 97 4/29/2026
1.2.25 116 4/27/2026
1.2.24 106 4/21/2026
1.2.23 119 4/14/2026
1.2.22 110 4/10/2026
1.2.21 106 4/10/2026
1.2.20 107 4/10/2026
1.2.19 110 4/9/2026
1.2.18 125 4/2/2026
1.2.17 109 4/1/2026
1.2.16 109 4/1/2026
1.2.15 108 3/31/2026
1.2.14 119 3/30/2026
Loading failed