Shaunebu.Common.RESTClient 1.0.8

There is a newer version of this package available.
See the version list below for details.
dotnet add package Shaunebu.Common.RESTClient --version 1.0.8
                    
NuGet\Install-Package Shaunebu.Common.RESTClient -Version 1.0.8
                    
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="Shaunebu.Common.RESTClient" Version="1.0.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Shaunebu.Common.RESTClient" Version="1.0.8" />
                    
Directory.Packages.props
<PackageReference Include="Shaunebu.Common.RESTClient" />
                    
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 Shaunebu.Common.RESTClient --version 1.0.8
                    
#r "nuget: Shaunebu.Common.RESTClient, 1.0.8"
                    
#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 Shaunebu.Common.RESTClient@1.0.8
                    
#: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=Shaunebu.Common.RESTClient&version=1.0.8
                    
Install as a Cake Addin
#tool nuget:?package=Shaunebu.Common.RESTClient&version=1.0.8
                    
Install as a Cake Tool

🧠 Shaunebu.Common.RESTClient

Platform License Production Ready Performance Easy

NuGet Version

NET Support Resilience Observability Serialization CSharp Architecture

FaultTolerance Caching ThreadSafe Offline

Support

🌐 Overview

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:

πŸ‘‰ it provides a full SDK-generation platform, ready for production, observability, resilience, and extensibility.

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 these capabilities through a unified runtime pipeline, with public entry points for DI, standalone clients, generated clients, and dynamic clients. It is declarative, extensible, and aligned with how .NET developers already configure HttpClient.

πŸ”§ How It Works

RESTClient can use either a source-generated client or a dynamic DispatchProxy client for an interface decorated with attributes.
Both paths now delegate to the same execution pipeline:

Generated Client ─┐
                  β”œβ”€> IRESTRequestExecutor
Dynamic Client β”€β”€β”€β”˜
                           ↓
                  RESTCachePipeline
                           ↓
                  IRESTRequestBuilder
                           ↓
                     Interceptors
                           ↓
                IRESTResiliencePipeline
                           ↓
                 HttpClient.SendAsync
                           ↓
                     Interceptors
                           ↓
                IRESTResponseProcessor
                           ↓
                   RESTReturnAdapter

Generated clients minimize per-call reflection and are the preferred path for trimming-sensitive applications. Dynamic clients remain available for runtime scenarios, but rely on DispatchProxy and reflection.

The unified pipeline provides:

  • per-method resilience

  • automatic serialization based on headers

  • observability hooks

  • request/response interceptors

  • fallback logic

  • caching

  • runtime validation

  • total control over HTTP behavior

Generated vs Dynamic

  • Generated Client is selected automatically when the source generator emits an implementation for your interface. It delegates to IRESTRequestExecutor and does not contain HTTP, serializer, fallback, cache, or response-processing logic.
  • Dynamic Client is used when no generated implementation is available or when you create one explicitly with RESTClientImpl<T>.Create. It captures the exact MethodInfo and arguments, then delegates to the same executor.
  • DI and standalone clients use the same request builder, serializer resolver, response processor, resilience pipeline, cache pipeline, return adapter, options, and interceptors.

Pipeline Ownership

  • Interceptors execute once per logical request from IRESTRequestExecutor; RESTClientDelegatingHandler is retained only as a compatibility type and is no longer registered by default.
  • Fallback is evaluated by the executor for HTTP errors and pipeline exceptions. External cancellation does not execute fallback.
  • Retry, timeout, circuit breaker, and bulkhead are coordinated by the internal resilience pipeline. Retries clone request messages and do not reuse a sent HttpRequestMessage.
  • Cache is coordinated by the internal cache pipeline. Cache keys are deterministic SHA256 values and do not expose argument values in clear text. Stream and HttpResponseMessage results are not cached.
  • Streams and raw responses preserve ownership: returned Stream/HttpResponseMessage values belong to the consumer; materialized responses are processed by the shared response processor.

Breaking Changes / Migration Notes

The unified pipeline preserves the main public consumer APIs, but changes which components participate in the default execution flow.

  • Generated clients no longer own HTTP execution, serialization, fallback, cache, or response processing. They delegate to IRESTRequestExecutor.
  • Dynamic clients use the same executor and should behave consistently with generated clients.
  • RESTClientDelegatingHandler, ClientHandler, ResilienceHandler, PolicyFactory, CachingInterceptor, FallbackInterceptor, and SuccessStatusCodeInterceptor are compatibility types. Do not use them for new application wiring.
  • Interceptors registered through DI run once per logical request from the executor.
  • Relative routes now require a valid base address from [Host]/absolute URI, HttpClient.BaseAddress, or RESTClientOptions.BaseAddress; otherwise RESTClientConfigurationException is thrown before send.
  • Dynamic clients remain reflection-based and are less trimming/AOT-friendly than generated clients.

See docs/MIGRATION-1.x.md for more detailed migration notes.


🎯 Design Principles

RESTClient is built on a clear philosophy:

1. Declarative by Default

Developers define behavior through attributes β€” not configuration files or boilerplate.

2. Production-Ready Out-of-the-Box

Retries, timeouts, logging, diagnostics, and telemetry are enabled automatically.

3. Extensible Everywhere

Interceptors, serializers, URL formatters, fallback providers β€” all pluggable.

4. Observable by Design

Built-in OpenTelemetry tracing, metrics, and structured logging support.

5. Safe Failure Behavior

Typed fallbacks and caching allow clients to operate gracefully under failure.

6. Multi-Format Native Support

JSON, XML, MessagePack, Protobuf, FormUrlEncoded, Multipart.

7. Zero Boilerplate

A single AddRESTClient<T>("https://api.example.com") registers the typed client, the configured HttpClient, and the unified pipeline.

πŸš€ Why Shaunebu.RESTClient?

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.

  • πŸ”§ Plug-and-play registration β€” just one line of DI setup.
  • 🧱 Enterprise resilience stack β€” retries, circuit breakers, timeouts, fallback providers.
  • πŸ“¦ Multi-format serialization β€” JSON, XML, MessagePack, Protobuf, UrlEncoded.
  • 🧩 Interceptor pipeline β€” security, logging, caching, metrics, OpenTelemetry.
  • 🌐 Dynamic content negotiation based on Accept headers.
  • πŸ’₯ Fallback providers that return typed responses without throwing exceptions.
  • ⏱️ Real upload/download progress tracking.
  • βš™οΈ Extensible architecture (custom interceptors, serializers, negotiators).

πŸ“¦ Installation

dotnet add package Shaunebu.Common.RESTClient
Package ID Current project version
Shaunebu.Common.RESTClient 1.0.7

Supported frameworks

The package currently targets:

Target Framework Status
net9.0 Supported
net10.0 Supported

Runtime dependencies

The package brings the runtime dependencies it needs through NuGet, including:

  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Http
  • Microsoft.Extensions.Caching.Memory
  • Microsoft.Extensions.Http.Polly
  • Polly
  • Newtonsoft.Json
  • MessagePack
  • Google.Protobuf / protobuf-net

🧭 Quick Start

using Microsoft.Extensions.DependencyInjection;
using Shaunebu.Common.RESTClient.Abstractions;
using Shaunebu.Common.RESTClient.Attributes;
using Shaunebu.Common.RESTClient.Clients;
using Shaunebu.Common.RESTClient.Extensions;
using Shaunebu.Common.RESTClient.Interceptors;

public interface IPostsApi
{
    [Get("/posts")]
    Task<List<Post>> GetPostsAsync();

    [Post("/posts")]
    Task<Post> CreatePostAsync([Body] CreatePostRequest request);
}

public sealed class Post
{
    public int Id { get; set; }
    public int UserId { get; set; }
    public string Title { get; set; } = "";
    public string Body { get; set; } = "";
}

public sealed class CreatePostRequest
{
    public int UserId { get; set; }
    public string Title { get; set; } = "";
    public string Body { get; set; } = "";
}

// Dependency Injection
var services = new ServiceCollection();
services.AddRESTClient<IPostsApi>("https://jsonplaceholder.typicode.com");

// Usage
using var provider = services.BuildServiceProvider();
var api = provider.GetRequiredService<IPostsApi>();
var result = await api.CreatePostAsync(new CreatePostRequest
{
    UserId = 1,
    Title = "Hello World",
    Body = "RESTClient is amazing!"
});

βœ… No manual handler wiring needed β€” serializers, resilience, cache, fallback, return adaptation, and interceptors are wired through the unified pipeline.


Official Sample

The official consumer sample lives in Shaunebu.Common.RESTClient.Sample. It demonstrates generated clients, dynamic clients, DI, standalone builder usage, RESTClientFactory, HTTP verbs, parameters, serializers, resilience, cache, fallback, interceptors, raw return types, IApiResponse<T>, and BaseAddress/host override behavior.


βš™οΈ Centralized Configuration

All global settings are managed through RESTClientOptions:

services.AddRESTClient(options =>
{
    options.BaseAddress = new Uri("https://api.example.com");
    options.Timeout.RequestTimeoutSeconds = 30;
    options.Resilience.RetryCount = 3;
    options.Resilience.CircuitBreakerThreshold = 5;
    options.DefaultCollectionFormat = CollectionFormat.Csv;
    options.BufferResponse = true;
});

Use services.AddRESTClient(options => ...) to register shared services and defaults. Use services.AddRESTClient<TClient>(baseAddress, options => ...) when you also want to register a typed client.

Client creation options

Use the public entry point that best fits your application:

// DI typed client. Uses a typed HttpClient configured with BaseAddress.
var services = new ServiceCollection();
services.AddRESTClient<IPostsApi>("https://jsonplaceholder.typicode.com");
using var provider = services.BuildServiceProvider();
var diClient = provider.GetRequiredService<IPostsApi>();

// Standalone generated/dynamic selection through the builder.
var standalone = RESTClientBuilder.ForStandalone<IPostsApi>(
    "https://jsonplaceholder.typicode.com");

// Existing HttpClient. The builder does not replace this instance.
using var httpClient = new HttpClient
{
    BaseAddress = new Uri("https://jsonplaceholder.typicode.com")
};
var generated = RESTClientBuilder.For<IPostsApi>(httpClient);

// Explicit dynamic client.
var dynamicClient = RESTClientImpl<IPostsApi>.Create(
    httpClient,
    new NullInterceptor());

// Factory from DI.
var factory = provider.GetRequiredService<IRESTClientFactory>();
var factoryClient = factory.CreateClient<IPostsApi>("https://jsonplaceholder.typicode.com");

AddRESTClient<TClient,TImplementation> is retained as a public overload for consumers that already use an explicit implementation type. The DI path still resolves the configured HttpClient through RESTClientBuilder.For<TClient>(httpClient, serviceProvider) so generated and dynamic clients use the same pipeline.

BaseAddress precedence

Relative routes such as [Get("/posts/{id}")] require an effective base address. The runtime resolves it consistently for generated and dynamic clients:

  1. Absolute URI in the method attribute or [Host("https://...")].
  2. HttpClient.BaseAddress configured for that client.
  3. RESTClientOptions.BaseAddress.
  4. RESTClientConfigurationException before HttpClient.SendAsync if the request is still relative.
public interface IHostOverrideApi
{
    [Get("/posts/{id}")]
    [Host("https://jsonplaceholder.typicode.com")]
    Task<Post> GetPostAsync(int id);
}

Example of appsettings.json registration

{
  "RestClients": [
    {
      "Interface": "MyApp.Api.IPostsApi, MyApp",
      "BaseUrl": "https://jsonplaceholder.typicode.com"
    }
  ]
}

Then:

services.AddRESTClientsFromConfig(Configuration);

🧩 Attribute Reference

Attribute Purpose
[Get], [Post], [Put], [Delete], [Patch], [Head], [Options] Declares HTTP methods
[Body], [Body(method: BodySerializationMethod.Xml)], [Body("text/plain")] Selects request body content type or built-in serialization mode
[Query(Name = "...", CollectionFormat = ...)] Controls query parameter naming and collection encoding
[Path("...")] Maps a parameter to a route placeholder when the CLR parameter name is not enough
[Host("https://...")] Overrides the default host per method
[Fallback(typeof(MyFallback))] Defines a typed fallback provider
[RetryPolicy], [Timeout], [CircuitBreaker], [Bulkhead] Method-level resilience controls
[Headers], [Header], [AliasAs] Header customization
[Multipart], [Multipart("BoundaryValue")] Multipart upload control
[RateLimit], [HealthCheck] Advanced traffic and availability controls

Feature Coverage Quick Reference

This mirrors the scenarios exercised by the official sample project.

public interface ISampleApi
{
    [Get("/posts/{id}")]
    Task<Post> GetPostAsync(int id);

    [Get("/posts")]
    Task<List<Post>> GetPostsAsync([Query(Name = "userId")] int userId);

    [Post("/posts")]
    Task<Post> CreatePostAsync([Body] CreatePostRequest request);

    [Put("/posts/{id}")]
    Task<Post> UpdatePostAsync(int id, [Body] CreatePostRequest request);

    [Patch("/posts/{id}")]
    Task<Post> PatchPostAsync(int id, [Body] CreatePostRequest request);

    [Delete("/posts/{id}")]
    Task DeletePostAsync(int id);

    [Head("/posts/{id}")]
    Task<HttpResponseMessage> HeadPostAsync(int id);

    [Options("/posts")]
    Task<HttpResponseMessage> OptionsPostsAsync();

    [Get("/comments")]
    Task<List<Comment>> GetCommentsAsync(int? postId = null, int? id = null);

    [Get("/posts")]
    Task<List<Post>> GetPostsByIdsAsync(
        [Query(Name = "id", CollectionFormat = CollectionFormat.Multi)] int[] ids);

    [Get("/users/{id}")]
    [Headers("X-Custom-Header: my-value")]
    Task<IApiResponse<User>> GetUserWithDetailsAsync(int id, [Header("api-key")] string apiKey);

    [Post("/post")]
    [Multipart]
    Task<string> UploadMultipartAsync(
        [AliasAs("file")] StreamPart file,
        [AliasAs("bytes")] byte[] bytes,
        [AliasAs("description")] string description);

    [Get("/posts/{id}")]
    Task<string> GetPostAsStringAsync(int id);

    [Get("/posts/{id}")]
    Task<byte[]> GetPostAsBytesAsync(int id);

    [Get("/posts/{id}")]
    Task<Stream> GetPostAsStreamAsync(int id);

    [Get("/posts/{id}")]
    Task<HttpResponseMessage> GetRawPostAsync(int id);
}

πŸ“œ Multi-Serialization and Content Negotiation

Shaunebu.RESTClient can serialize and deserialize payloads dynamically based on body attributes, [Serializer], request content type, and response Content-Type headers.

public interface IUsersApi
{
    [Post("/users")]
    Task<User> AddUser([Body] User user);

    [Post("/users/xml")]
    Task<User> AddUserAsXml([Body(method: BodySerializationMethod.Xml)] User user);

    [Post("/files/upload")]
    [Multipart("ShaunebuBoundary")]
    Task<string> UploadFile([AliasAs("file")] StreamPart file);
}

Supported Serializers

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.AddSingleton<IContentSerializer, MyCustomSerializer>();
services.AddRESTClient<IMyApi>("https://api.example.com");

🧠 Dynamic Content Negotiation

The ContentNegotiator resolves serializers from request content types, response Content-Type headers, [Serializer], and registered custom serializers. Accept can still be declared with [Headers] or [Accept] for APIs that negotiate response formats. Example:

public interface IDataApi
{
    [Get("/data")]
    [Headers("Accept: application/x-protobuf")]
    Task<MyResponse> GetDataInProtobuf();
}

The library will automatically:

  • use ProtobufSerializer when the response content type resolves to protobuf,

  • and use the configured serializer for matching request bodies.


πŸ’ͺ Declarative Resilience (Polly Built-In)

Built-in attribute-level resilience, powered by Polly:

public interface IDataApi
{
    [Get("/data")]
    [RetryPolicy(retryCount: 3, sleepDurationInMs: 1000)]
    [Timeout(timeoutInMs: 5000)]
    [CircuitBreaker(exceptionsAllowedBeforeBreaking: 3, durationOfBreakInMs: 10000)]
    Task<DataResponse> GetDataAsync();
}

No need to register Polly handlers manually β€” resilience is coordinated by the unified executor pipeline.


πŸ’₯ Typed Fallback Providers

If a request fails, the fallback provider can return a typed result instead of throwing an exception.

public interface IUsersApi
{
    [Get("/users/{id}")]
    [Fallback(typeof(UserFallbackProvider))]
    Task<User> GetUserAsync(int id);
}

public class UserFallbackProvider : IFallbackProvider<User>
{
    public Task<User> GetFallbackAsync(object[] args) =>
        Task.FromResult(new User { Id = -1, Name = "Offline User" });
}

When triggered, the response includes a header:

X-Fallback-Executed: true

⏱️ Progress Tracking

Real-time upload and download progress tracking using IProgress<double>:

public interface IUploadApi
{
    [Post("/upload")]
    [Multipart]
    Task<string> UploadLargeFileAsync([AliasAs("file")] StreamPart file, IProgress<double> progress);
}

The progress value reports 0.0 β†’ 1.0 (0% β†’ 100%).


πŸ”„ Interceptors and Middleware

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.AddSingleton<IRESTInterceptor, MyLoggingInterceptor>();
services.AddRESTClient<IMyApi>("https://api.example.com");

Default DI interceptors

  • πŸ” SecurityInterceptor

  • πŸ”‘ AuthorizationInterceptor

  • 🧭 OpenTelemetryInterceptor

  • πŸͺ΅ LoggingInterceptor

  • πŸ§ͺ DebugInterceptor

  • βš™οΈ PollyDebugInterceptor

  • πŸ“Š MetricsInterceptor

CachingInterceptor, FallbackInterceptor, ResilienceInterceptor, RESTClientDelegatingHandler, and related legacy handler types are retained for compatibility, but they are not the default execution path after the pipeline unification. Cache, fallback, and resilience are coordinated by the executor pipeline.


🧱 Caching and Metrics

Out of the box support for:

  • ICacheStore (default: MemoryCacheStore)

  • [Cache] through the internal RESTCachePipeline

  • OpenTelemetry hooks (OpenTelemetryInterceptor)

  • Metrics gathering (MetricsInterceptor)


πŸ“Š Full Feature Comparison

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 βœ… ❌
[RetryPolicy], [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 βœ… ❌
ApiException<T> and typed fallback support βœ… ⚠️
Source generator + dynamic proxy client paths βœ… βœ…
Production-Ready Stack βœ… ❌

🧠 Deep Comparison with Refit

While Refit is excellent for lightweight API calls, it has limitations in enterprise scenarios:

Refit is ideal for:

  • Simple REST wrappers

  • Mobile apps

  • Low-infrastructure projects

  • Rapid prototypes

RESTClient is designed for:

  • 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.


🧩 Extending Shaunebu.RESTClient

Custom Serializer

Implement IContentSerializer to plug in any serialization logic.

Custom Interceptor

Add your own interceptor for metrics, logging, or security checks.

Custom Fallback Provider

Attach to [Fallback(typeof(...))] for custom recovery logic.

Custom Url Parameter Formatter

Implement IUrlParameterFormatter to change query escaping globally.


🧩 Example: Complex Client Definition

[Headers("Authorization: Bearer")]
[Host("https://api.override.com")]
public interface IComplexApi
{
    [Get("/users/{id}")]
    [Fallback(typeof(UserFallback))]
    [RetryPolicy(retryCount: 3, sleepDurationInMs: 1000)]
    [Timeout(timeoutInMs: 10000)]
    Task<User> GetUserAsync(int id);

    [Post("/upload")]
    [Multipart("ShaunebuBoundary")]
    Task<string> 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


🧩 Credits

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.


πŸ—ΊοΈ Current Status

Capability Status Notes
Unified generated + dynamic pipeline βœ… Completed Both paths delegate to IRESTRequestExecutor.
Source generator integration βœ… Completed Generated clients are preferred for trimming-sensitive applications.
Dynamic DispatchProxy client βœ… Completed Available through RESTClientImpl<T>.Create and fallback creation paths.
DI / standalone / factory creation βœ… Completed AddRESTClient, RESTClientBuilder, and RESTClientFactory share the same executor path.
JSON, XML, URL encoded, MessagePack, Protobuf, custom serializers βœ… Completed Serializer resolution is centralized through ISerializerResolver and ContentNegotiator.
Retry, timeout, circuit breaker, bulkhead, cache, fallback βœ… Completed Coordinated by the unified pipeline and method attributes.
Memory cache store βœ… Completed MemoryCacheStore is registered by default.
Distributed cache adapter βœ… Available DistributedCacheStore exists, but Redis/SQLite providers are not bundled here.
Dashboard, CLI import, analyzer package, SignalR bridge 🚧 Not included Do not rely on these as current runtime features.

πŸ“„ License

MIT Β© Shaunebu 2026

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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.0.9 0 7/21/2026
1.0.8 0 7/21/2026
1.0.7 0 7/21/2026
1.0.6 100 7/10/2026
1.0.5 98 7/10/2026
1.0.4 95 7/9/2026
1.0.3 132 4/13/2026
1.0.2 111 4/13/2026
1.0.1 726 12/1/2025
1.0.0 693 12/1/2025