AspectCentral.DispatchProxy
2.0.0-rc.10
See the version list below for details.
dotnet add package AspectCentral.DispatchProxy --version 2.0.0-rc.10
NuGet\Install-Package AspectCentral.DispatchProxy -Version 2.0.0-rc.10
<PackageReference Include="AspectCentral.DispatchProxy" Version="2.0.0-rc.10" />
<PackageVersion Include="AspectCentral.DispatchProxy" Version="2.0.0-rc.10" />
<PackageReference Include="AspectCentral.DispatchProxy" />
paket add AspectCentral.DispatchProxy --version 2.0.0-rc.10
#r "nuget: AspectCentral.DispatchProxy, 2.0.0-rc.10"
#:package AspectCentral.DispatchProxy@2.0.0-rc.10
#addin nuget:?package=AspectCentral.DispatchProxy&version=2.0.0-rc.10&prerelease
#tool nuget:?package=AspectCentral.DispatchProxy&version=2.0.0-rc.10&prerelease
AspectCentral.DispatchProxy
Lightweight, dependency-injection-native Aspect-Oriented Programming (AOP) for .NET, built on
System.Reflection.DispatchProxy.
AspectCentral.DispatchProxy lets you wrap any interface-based service registered in
Microsoft.Extensions.DependencyInjection with one or more cross-cutting concerns — logging,
profiling, caching, validation, retry, authorization — without modifying the underlying class.
Method calls go through a transparent runtime proxy that invokes your PreInvoke / PostInvoke
hooks around the original implementation.
The library is small (one assembly), allocation-conscious (uses the [LoggerMessage] source
generator), and ships first-class observability via the platform-standard
ActivitySource and
Meter — so traces and
metrics light up automatically when a consumer wires up OpenTelemetry.
Table of Contents
- Why AspectCentral?
- Installation
- Concepts
- Quick Start
- Built-in Aspects
- Writing a Custom Aspect
- Method Filtering
- Async Method Support
- Observability
- How It Works (Internals)
- Target Frameworks & Compatibility
- Building, Testing, Packing
- Versioning
- Contributing
- License
Why AspectCentral?
Most .NET AOP libraries fall into one of two camps:
- IL-rewriting / weaving (PostSharp, Fody) — powerful but requires a build-time tool, can be hard to debug, and changes your assemblies.
- Castle DynamicProxy / Reflection.Emit — runtime emit, large surface, dependency on
Castle.Core.
AspectCentral.DispatchProxy takes a third path: it leans on the BCL's built-in
System.Reflection.DispatchProxy, which is purpose-built for transparent interface proxying.
That gives you:
- No build-time weaving. Your assemblies are untouched on disk.
- No third-party proxy runtime. Only BCL primitives and
Microsoft.Extensions.*. - DI-native. You compose aspects fluently against
IServiceCollection. - Fully transparent. Consumers resolve
IMyServiceand get a proxy — no API change. - Async-aware. Sync,
Task, andTask<T>methods are all dispatched correctly soPostInvokeruns after the awaited result, not at the synchronous return of the proxy. - Observable by default. Every intercepted call emits an
Activityand records duration / invocation metrics. Hook them up to OpenTelemetry with one line.
Installation
dotnet add package AspectCentral.DispatchProxy
Note: v2.0.0 (the modernization release that introduced
net9.0/net10.0support, the async short-circuit fix, and the keyed-DI guards) has not yet been published to nuget.org. Until the first stable v2 release ships, consume the library by either (a) building it from source against thedevelopbranch, or (b) referencing the*.nupkgartifact published by the Azure Pipelines build (azure-pipelines.yml) — it is signed and uploaded as a build artifact on every CI run. Thedotnet add packagecommand above will continue to resolve to v1.x for now.
This package depends on the AspectCentral.Abstractions package, which contributes
IAspectRegistrationBuilder, AspectConfiguration, AspectContext, MethodTypeOptions, and
related primitives. It is pulled in transitively — you do not need to install it explicitly.
Concepts
| Concept | Type | Role |
|---|---|---|
| Aspect | BaseAspect<T> |
The proxy itself. Holds the wrapped instance and exposes PreInvoke / PostInvoke hooks around every intercepted call. |
| Aspect Factory | IAspectFactory / BaseAspectFactory |
Knows how to construct an aspect of a given type around a given service instance. Registered as a singleton in DI and resolved per-request when the proxy chain is composed. |
| Registration Builder | IAspectRegistrationBuilder (from AspectCentral.Abstractions), implemented here by DispatchProxyAspectRegistrationBuilder |
Fluent API for declaring "wrap service X with aspects A, B, C." Builds an AspectConfiguration. |
| Aspect Configuration | AspectConfiguration |
The data model: which factories apply to which (serviceType, implementationType) pair, in what order, and which methods to intercept. |
| Aspect Configuration Provider | IAspectConfigurationProvider |
Read-only view of the configuration consulted at proxy construction time and on every call (ShouldIntercept(...)). |
| Aspect Context | AspectContext |
Per-call state: the target MethodInfo, arguments, return value, async kind, and a human-readable InvocationString for logs. |
The proxy chain is composed inside out: the innermost call is the real service; each registered aspect wraps the previous proxy in the order they were added.
Quick Start
1. Register your service and add aspect support
using AspectCentral.DispatchProxy;
using AspectCentral.DispatchProxy.Logging;
using AspectCentral.DispatchProxy.Profiling;
var services = new ServiceCollection();
services.AddLogging(); // ILogger<T> is required by aspects
services.AddSingleton<IGreeter, Greeter>(); // your real service
services.AddAspectSupport() // returns IAspectRegistrationBuilder
.AddLoggingAspect() // every interface registered above gets logging
.AddProfilingAspect(); // ...and profiling, in that order
AddAspectSupport():
- Registers an in-memory
IAspectConfigurationProvider. - Reflectively discovers every concrete
IAspectFactoryin loaded assemblies and registers each as a singleton in DI. - Returns a
DispatchProxyAspectRegistrationBuilderyou can chain against.
2. Resolve the service — get a proxy transparently
var provider = services.BuildServiceProvider();
var greeter = provider.GetRequiredService<IGreeter>(); // this is a DispatchProxy
greeter.Greet("Rudy");
You did not change IGreeter or Greeter. You did not call Create yourself. The DI container
hands you a proxy that calls LoggingAspect → ProfilingAspect → Greeter.Greet.
3. Apply aspects to specific services only
services.AddAspectSupport()
.AddLoggingAspect(typeof(IGreeter).GetMethod(nameof(IGreeter.Greet))!) // only this method
.AddAspectViaFactory<MyAuditAspectFactory>(); // for everything
AddAspectViaFactory<TFactory>(int? sortOrder = null, params MethodInfo[] methodsToIntercept)
gives you full control over both ordering and method filtering. Lower sortOrder runs first
(closest to the caller).
Built-in Aspects
Logging Aspect
LoggingAspect<T> writes {Method}(args) Start, Return value : {value} (when the method has a
return value), and {Method}(args) End at Information level using the source-generated
AspectLogs log methods (event IDs 2001 / 2002 / 2003).
Register it in one of two ways:
services.AddAspectSupport().AddLoggingAspect(); // all DI-registered interfaces
services.AddAspectSupport().AddLoggingAspect(targetMethod); // only the listed methods
services.AddAspectSupport().AddAspectViaFactory<LoggingAspectFactory>(sortOrder: 100);
The logger category is the implementation type's FullName (e.g. MyApp.Services.Greeter) so you
can filter by category in any logging provider.
Profiling Aspect
ProfilingAspect<T> starts a Stopwatch in PreInvoke and writes Runtime hh:mm:ss.ff in
PostInvoke (event IDs 3001 / 3002). It works correctly for Task and Task<T> methods —
the stopwatch stops after the awaited completion, not after the proxy synchronously hands back
the Task.
services.AddAspectSupport().AddProfilingAspect();
services.AddAspectSupport().AddProfilingAspect(method1, method2);
services.AddAspectSupport().AddAspectViaFactory<ProfilingAspectFactory>(sortOrder: 200);
Order matters
When you stack AddLoggingAspect().AddProfilingAspect() the proxy chain at runtime is
Caller → Logging → Profiling → real service. The profiling timer therefore measures only the
real method, not the logging overhead. Reverse the calls and the logging output will be
included in the timing.
Writing a Custom Aspect
A custom aspect is two small types: an aspect (extends BaseAspect<T>) and a factory
(extends BaseAspectFactory). The factory is auto-discovered and registered when the consumer
calls AddAspectSupport() — you don't need a registration extension method, but providing one
keeps the call site fluent.
using System.Reflection;
using AspectCentral.Abstractions;
using AspectCentral.Abstractions.Configuration;
using AspectCentral.DispatchProxy;
using Microsoft.Extensions.Logging;
public sealed class AuditAspect<T> : BaseAspect<T> where T : class?
{
public static readonly Type Type = typeof(AuditAspect<>);
public static T Create(
T instance,
Type implementationType,
ILoggerFactory loggerFactory,
IAspectConfigurationProvider provider,
Type factoryType)
{
var proxy = (AuditAspect<T>)(object)Create<T, AuditAspect<T>>();
proxy.Instance = instance;
proxy.ObjectType = implementationType;
proxy.Logger = loggerFactory.CreateLogger(implementationType.FullName ?? implementationType.Name);
proxy.AspectConfigurationProvider = provider;
proxy.FactoryType = factoryType;
return (T)(object)proxy;
}
public override void PreInvoke(AspectContext ctx)
=> Logger.LogInformation("[AUDIT] {User} -> {Call}", Environment.UserName, ctx.InvocationString);
public override void PostInvoke(AspectContext ctx)
=> Logger.LogInformation("[AUDIT] {Call} completed", ctx.InvocationString);
}
public sealed class AuditAspectFactory(ILoggerFactory lf, IAspectConfigurationProvider p)
: BaseAspectFactory(lf, p)
{
public static readonly Type AuditAspectFactoryType = typeof(AuditAspectFactory);
// Note: override methods cannot restate the generic constraint (CS0460);
// the `where T : class?` from BaseAspectFactory.Create<T> is inherited.
public override T Create<T>(T instance, Type implementationType)
=> AuditAspect<T>.Create(instance, implementationType, LoggerFactory, AspectConfigurationProvider, AuditAspectFactoryType);
}
// optional: a fluent extension for nicer call sites
public static class AuditRegistrationExtensions
{
public static IAspectRegistrationBuilder AddAuditAspect(
this IAspectRegistrationBuilder builder,
params MethodInfo[] methodsToIntercept)
{
ArgumentNullException.ThrowIfNull(builder);
builder.AddAspect(AuditAspectFactory.AuditAspectFactoryType, methodsToIntercept: methodsToIntercept);
return builder;
}
}
You can short-circuit the underlying call by setting aspectContext.InvokeMethod = false and
populating aspectContext.ReturnValue yourself in PreInvoke — useful for caching aspects.
Method Filtering
There are two filtering mechanisms, applied in this order:
- Per-aspect method list — supplied at registration time via the
params MethodInfo[] methodsToInterceptoverload. An empty array means every method on the interface. IAspectConfigurationProvider.ShouldIntercept(...)— consulted on every call. The default in-memory provider checks the per-aspect method list, so if you supply an explicit list, only methods in that list will triggerPreInvoke/PostInvoke. Methods that fail the check are still dispatched to the underlying service — they are just not wrapped.
var greet = typeof(IGreeter).GetMethod(nameof(IGreeter.Greet))!;
services.AddAspectSupport().AddLoggingAspect(greet); // only Greet is logged
Async Method Support
BaseAspect<T>.Invoke inspects the target method's return type and dispatches accordingly:
| Return type | Dispatch path | When PostInvoke runs |
|---|---|---|
Synchronous (e.g. int, void) |
Process |
Inline, after the call returns. |
Task (async action) |
ProcessAction |
After the returned Task is awaited inside the proxy. |
Task<TResult> (async function) |
ProcessFunctionAsync<TResult> (resolved reflectively) |
After the awaited result is captured into aspectContext.ReturnValue. |
This means a profiling aspect on an async Task<T> method correctly measures the end-to-end
async duration, and a logging aspect logs the awaited result, not the unfinished Task.
ValueTask / ValueTask<T> are not currently a first-class case — they fall through to the
synchronous path.
Observability
The library exposes a single ActivitySource and a single Meter. Both are also exposed as
public const string so consumers can register them without referencing internal fields.
Activity Source
AspectCentral.DispatchProxy.Aspects — every intercepted call opens an Activity named
{InterfaceName}.{MethodName} with the following tags:
| Tag | Description |
|---|---|
code.namespace |
Namespace of the interface declaring the method. |
code.function |
Method name. |
aspect.factory |
Full type name of the factory that built the proxy. |
aspect.target_type |
Full type name of the underlying implementation. |
aspect.interface_type |
Full type name of the proxied interface. |
When the underlying call throws, the activity status is set to Error and an exception event
is recorded with exception.type, exception.message, and exception.stacktrace.
Meter
AspectCentral.DispatchProxy.Aspects exposes:
| Instrument | Type | Unit | Tags | Purpose |
|---|---|---|---|---|
aspect.invocations |
Counter<long> |
(none) | aspect, status (success/error) |
Total intercepted calls. |
aspect.invocation_duration |
Histogram<double> |
ms |
aspect |
Per-call wall-clock duration. |
Wiring up OpenTelemetry
using AspectCentral.DispatchProxy.Telemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
services.AddOpenTelemetry()
.WithTracing(t => t.AddSource(LibraryActivitySources.All.ToArray()))
.WithMetrics(m => m.AddMeter(LibraryMeters.All.ToArray()));
LibraryActivitySources.All and LibraryMeters.All are IReadOnlyList<string> collections so
you can splat them into any OTel pipeline regardless of how many sources the library grows in the
future.
How It Works (Internals)
The pipeline runs in three phases:
Phase 1 — Registration
services.AddAspectSupport() does the following:
- Calls
GetOrAddInMemoryProvider, which:- Returns any existing non-keyed
IAspectConfigurationProviderthat was previously registered as anImplementationInstancesingleton — the returned instance is whatever concrete type was registered (not necessarilyInMemoryAspectConfigurationProvider). - Throws
InvalidOperationExceptionif anIAspectConfigurationProvideris already registered via type or factory (the existing instance cannot be observed up-front, so silent reuse would risk a provider mismatch at runtime). - Otherwise instantiates a new
InMemoryAspectConfigurationProviderand registers it viaAddSingleton<IAspectConfigurationProvider>(provider).
- Returns any existing non-keyed
- Scans every loaded assembly via
AppDomain.CurrentDomain.GetAssemblies()and finds every concreteIAspectFactory. Each isTryAddSingleton-registered against itself. - Returns a
DispatchProxyAspectRegistrationBuilderfor fluent chaining.
When you call .AddLoggingAspect() (or .AddAspectViaFactory<T>()), the builder records an
AspectConfiguration entry: (serviceType → implementationType → [factoryType, sortOrder, methods]).
Phase 2 — Service-collection rewrite
When the service provider is built, ConfigureAspects (called from
AddAspectSupport(IAspectConfigurationProvider)) walks the IServiceCollection. For every
descriptor whose ServiceType is an interface and whose ImplementationType has an
AspectConfiguration, it:
- Re-registers the implementation type as itself (concrete) with the original
Lifetimeso the real instance can still be resolved. - Replaces the original descriptor with a factory delegate that calls
InvokeCreateFactory(...)→CreateFactory<TService>(...).
CreateFactory<TService> composes the proxy chain by iterating the configured aspects in order
and wrapping each successive instance via IAspectFactory.Create.
Phase 3 — Invocation
When a method is called on the outermost proxy, BaseAspect<T>.Invoke:
- Builds an
AspectContext(resolves the implementationMethodInfo, builds anInvocationString, setsMethodTypeto sync / async-action / async-function). - Starts an
Activityand tags it. - Asks
IAspectConfigurationProvider.ShouldIntercept(...).- If yes →
PreInvoke→ dispatch onMethodTypeOptions→PostInvoke(inline for sync; viaContinueWith/ async continuation forTask/Task<T>). - If no → dispatch directly without hooks.
- If yes →
- Records
aspect.invocationsandaspect.invocation_duration. - Re-throws on exception with the activity marked
Error.
The MethodInfo cache (JamesConsulting.Constants.TypeMethods) is populated lazily per
ObjectType to avoid repeated Type.GetMethods() calls on the hot path.
Target Frameworks & Compatibility
The current package multi-targets:
net9.0net10.0netstandard2.1
netstandard2.1 covers .NET Core 3.x, Mono, and Xamarin / Unity workloads where applicable. The
modern TFMs unlock source-generated logging and [ExcludeFromCodeCoverage] enhancements.
Building, Testing, Packing
The repository is a standard dotnet solution with a single library project and a single test
project (xUnit).
# Restore + build
dotnet restore
dotnet build AspectCentral.DispatchProxy.sln --configuration Debug --no-restore
# Run all tests across every TFM the test project targets
dotnet test AspectCentral.DispatchProxy.Tests/AspectCentral.DispatchProxy.Tests.csproj
# Run tests for a single TFM
dotnet test AspectCentral.DispatchProxy.Tests/AspectCentral.DispatchProxy.Tests.csproj --framework net10.0
# Run a single class or method (xUnit filter syntax)
dotnet test --filter "FullyQualifiedName~BaseAspectTests"
# Pack the NuGet (the workflow runs the equivalent of this)
dotnet pack AspectCentral.DispatchProxy/AspectCentral.DispatchProxy.csproj --configuration Release
CI runs in GitHub Actions via a single workflow:
.github/workflows/ci.yml. It contains three jobs:
build-test— runs on every push todevelop,main,master,feature/**,release/**,hotfix/**, on PRs todevelop/main/master, and onv*tags. Restores, builds, tests onnet9.0+net10.0, runs SonarCloud (whenvars.SONAR_PROJECT_KEYis set), and uploads a preview*.nupkgartifact suffixed-ci.<run_number>.publish-rc—needs: build-test. Runs only onrelease/**branches. Signs the package with Azure Trusted Signing (OIDC, no secrets) and pushes to nuget.org via NuGet Trusted Publishing (OIDC, no API key).publish-stable—needs: build-test. Runs only onv*tags. Same sign/push as RC plus a GitHub Release. Verifies the tag matches<VersionPrefix>and that the tagged commit is reachable frommaster.
Both publish jobs gate on build-test succeeding first — there is no way to ship an
untested package. See Release Process below.
Release Process
This repository follows GitFlow with separate RC and stable channels, both driven by
the single workflow .github/workflows/ci.yml. The git ref
decides which publish job runs (after build-test passes).
Channels
| Trigger | Job | Version produced | Example |
|---|---|---|---|
Push to release/X.Y.Z branch |
publish-rc |
<VersionPrefix>-rc.<run_number> |
2.0.0-rc.42 |
Push tag vX.Y.Z |
publish-stable |
<VersionPrefix> |
2.0.0 |
<VersionPrefix> is the value in
AspectCentral.DispatchProxy.csproj
(currently 2.0.0). The two channels share that single source of truth — bumping it bumps both.
End-to-end flow for a new release
- Cut the release branch from
develop:
→git checkout develop && git pull git checkout -b release/2.0.0 git push -u origin release/2.0.0publish-rcships2.0.0-rc.1to nuget.org. - Land bug fixes on
release/2.0.0(PR or direct push). Each push ships2.0.0-rc.2,-rc.3, … so consumers can validate against real RCs. - Bump
<VersionPrefix>in the.csprojonly when the next release should change (e.g. you decide to ship2.0.1instead of2.0.0). Patch/minor RCs of the same target stay on the same prefix. - When the RC is accepted:
- PR
release/2.0.0→master. Merge. - Tag the merge commit on
master:
→git checkout master && git pull git tag v2.0.0 && git push origin v2.0.0publish-stableships2.0.0to nuget.org and creates a GitHub Release. - Back-merge
release/2.0.0→developso any release-only fixes flow back.
- PR
Safety checks on publish-stable
- Tag must match
<VersionPrefix>.v2.0.0against a csproj of<VersionPrefix>2.0.1</VersionPrefix>fails before signing. - Tag commit must be reachable from
master. Tagging a feature branch fails the workflow.
What never publishes
- Pushes to
develop,feature/**,hotfix/**, or PRs — these only runci.yml. - Pushes to
release/**whose CI fails —publish-rcruns in the same workflow as build/test and stops on the first failed step.
Required GitHub configuration
The release GitHub Environment must exist on the repo (it scopes the OIDC subject claim,
satisfies the nuget.org Trusted Publishing policy, and gates secret access). Configuration is
shared at the org level:
- Org secrets:
AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_SUBSCRIPTION_ID,NUGET_USER - Org variables:
TRUSTED_SIGNING_ENDPOINT,TRUSTED_SIGNING_ACCOUNT,TRUSTED_SIGNING_PROFILE(and optionallySONAR_PROJECT_KEY)
The Entra app registration backing AZURE_CLIENT_ID needs a federated credential whose
subject is repo:jamesconsultingllc/AspectCentral.DispatchProxy:environment:release.
Versioning
The package follows Semantic Versioning:
| Change | Bump |
|---|---|
| Bug fix, perf improvement, internal refactor | Patch (x.y.Z) |
| New public API (backward-compatible) | Minor (x.Y.0) |
| Removal, signature change, or constraint tightening on public API | Major (X.0.0) |
Constraint changes on public generics (e.g. where T : class? → where T : class) are
treated as breaking and require a major bump.
Contributing
- Fork and create a feature branch from
develop(feature/short-name). - Add a failing test under
AspectCentral.DispatchProxy.Testsfirst — this repository follows TDD and BDD perAGENTS.md. - Make the smallest change that turns the test green.
- Run
dotnet testacross every TFM. Architecture tests underArchitectureTests.csenforce that the library does not depend on ASP.NET Core, hosting, or HTTP abstractions. - Open a PR back into
develop.
For larger contributions please open an issue first to discuss the design.
License
Released under the MIT License. © James Consulting LLC.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. 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. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- AspectCentral.Abstractions (>= 1.0.2)
- JamesConsulting (>= 1.0.1)
- MessagePack (>= 2.5.187)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.2)
- Microsoft.Extensions.Options (>= 9.0.2)
- System.Diagnostics.DiagnosticSource (>= 9.0.2)
-
net10.0
- AspectCentral.Abstractions (>= 1.0.2)
- JamesConsulting (>= 1.0.1)
- MessagePack (>= 2.5.187)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.2)
- Microsoft.Extensions.Options (>= 9.0.2)
-
net9.0
- AspectCentral.Abstractions (>= 1.0.2)
- JamesConsulting (>= 1.0.1)
- MessagePack (>= 2.5.187)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.2)
- Microsoft.Extensions.Options (>= 9.0.2)
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 |
|---|---|---|
| 2.0.0-rc.57 | 40 | 5/23/2026 |
| 2.0.0-rc.10 | 69 | 5/7/2026 |
| 2.0.0-rc.9 | 52 | 5/7/2026 |
| 1.0.1 | 727 | 11/12/2021 |
| 1.0.0 | 534 | 10/15/2021 |
| 1.0.0-alpha3 | 570 | 4/6/2020 |
| 1.0.0-alpha2 | 614 | 8/29/2019 |
| 1.0.0-alpha1 | 633 | 8/18/2019 |