MediatorBuilder fluent configuration API — New AddMediator(Action<MediatorBuilder> configure) overload (source-generated) that provides a single entry point for mediator registration. The builder automatically calls RegisterMediatorHandlers(), applies the user’s configuration, calls RegisterPipelineChains(), and freezes the dispatch tables — replacing the previous 3-step manual setup. Six fluent methods:
AddOpenBehavior(Type, ServiceLifetime) — registers open-generic IPipelineBehavior<,> (e.g., typeof(LoggingBehavior<,>))AddStreamBehavior<T>(ServiceLifetime) — registers closed IStreamPipelineBehavior<TRequest, TResponse>AddRequestPreProcessor<T>(ServiceLifetime) — registers IRequestPreProcessor<TRequest>AddRequestPostProcessor<T>(ServiceLifetime) — registers IRequestPostProcessor<TRequest, TResponse>AddRequestExceptionHandler<T>(ServiceLifetime) — registers IRequestExceptionHandler<TRequest, TResponse>AddParallelNotificationPublisher() — replaces sequential publisher with Task.WhenAll parallel dispatchAll generic methods are annotated with [DynamicallyAccessedMembers] for Native AOT safety. Services property is publicly accessible for advanced DI scenarios within the builder callback.
services.AddMediator(builder =>
{
builder.AddOpenBehavior(typeof(LoggingBehavior<,>));
builder.AddRequestPreProcessor<ValidationPreProcessor>();
builder.AddParallelNotificationPublisher();
});
DSoftStudio.Mediator, DSoftStudio.Mediator.Abstractions, DSoftStudio.Mediator.FluentValidation, DSoftStudio.Mediator.HybridCache, DSoftStudio.Mediator.OpenTelemetry) are now signed with PublicKeyToken=6c7e753832e8eb05. Enables installation in GAC, use from other strong-named assemblies, and tamper detection. Key file: DSoftStudio.Mediator.snk with InternalsVisibleTo attributes updated across all projects. See ADR-0003.RegisterMediatorHandlers() or PrecompilePipelines() are called alongside AddMediator(Action<MediatorBuilder>). The builder overload already performs these operations internally — calling them separately causes redundant registrations. The diagnostic identifies the specific redundant call and explains what the builder handles automatically.RegisterMediatorHandlers() and RegisterPipelineChains() now detect duplicate invocations via a per-IServiceCollection sentinel pattern (private __Sentinel / __PipelineSentinel marker types registered as singletons). If the sentinel is already present, the method returns immediately — preventing double handler/pipeline registration when AddMediator(configure) is used alongside legacy calls. The sentinel approach was chosen over static bool guards to preserve test isolation across parallel test classes with independent ServiceCollection instances.DSoftStudio.Mediator.FluentValidation, DSoftStudio.Mediator.HybridCache, DSoftStudio.Mediator.OpenTelemetry updated to depend on DSoftStudio.Mediator >= 1.2.0-rc.1. All companion assemblies are now strong-named.AnalyzerReleases.Unshipped.md under Release 1.2.0-rc.1..snk key for enterprise compatibility, GAC installation, and InternalsVisibleTo with public key verification. See docs/mediator/adr/0003-strong-naming.md.IRequest<T>, ICommand<T>, and IQuery<T> types with no corresponding IRequestHandler<TRequest, TResponse> implementation. Works across project boundaries via ReferencedAssemblyScanner and also recognizes self-handling requests (static Execute). Catches orphan request types before runtime InvalidOperationException.ICommand<T> or IQuery<T> instead of IRequest<T> for improved intent clarity. Zero runtime cost — purely informational at build time.NullableFullyQualifiedFormat) for all handler registrations, interceptors, and typed extensions. Types like IRequest<string?>, IRequest<List<int?>?>, and IRequest<(string? Name, int? Age)?> are correctly propagated through the entire pipeline.DependencyInjectionDiagnosticTests (12 tests covering DSOFT001/002/003), ReferencedAssemblyDiagnosticTests (6 tests covering DSOFT001/005 with 3-assembly pattern), CqrsSemanticAnalyzerTests (10 tests for DSOFT006).NullableResponseTests) and 4 cross-assembly tests (NullableCrossAssemblyTests) validating nullable type propagation through handlers, interceptors, and cross-project discovery.docs/mediator/architecture/production-validation.md page documenting all 48 enterprise integration tests organized by category with direct links to source.NullableCrossAssemblyDiscoveryTests — 2 Roslyn in-memory regression tests covering both cross-assembly discovery paths: Phase 1 (attribute-based, where typeof() strips nullable — the bug path) and Phase 2 (type-based, where AllInterfaces preserves nullable from PE metadata). Both verify generated DI registrations contain the correct global::User?> annotation.DependencyInjectionGenerator, SendInterceptorGenerator, PublishInterceptorGenerator, StreamInterceptorGenerator, CqrsSemanticAnalyzer, ReferencedAssemblyScanner) now use NullableFullyQualifiedFormat instead of SymbolDisplayFormat.FullyQualifiedFormat, preserving ? annotations in all emitted code.docs/mediator/benchmarks.md.typeof() inside [MediatorHandlerRegistration] assembly attributes strips nullable reference type annotations at the IL level — e.g., typeof(IRequestHandler<GetUser, User?>) becomes typeof(IRequestHandler<GetUser, User>) in metadata. This caused CS8631 warnings and false DSOFT001 diagnostics when a test project referenced a project containing handlers with nullable response types. The fix in ReferencedAssemblyScanner.CollectHandlersFromAttributes() re-resolves the service type from implType.AllInterfaces (which preserves nullable annotations via PE metadata NullableAttribute), using SymbolEqualityComparer.Default to match ignoring nullable differences.FlakeyPingHandler.FailuresRemaining with injectable FlakeyState class, introduced IChaosRandom interface for deterministic chaos tests, added [Trait("Category", "NonDeterministic")] to allocation tests, increased timeout margins with Debugger.IsAttached awareness, and strengthened expression tree tests with Compile() + MethodCallExpression verification.AnalyzerReleases.Unshipped.md to AnalyzerReleases.Shipped.md under Release 1.1.8-rc.1.DirectCall_WithPipeline fair baseline running in the same isolated BenchmarkDotNet process. Shared infrastructure: CreateOrderCommand message types, FakeOrderRepository with simulated async I/O.DSoftSendObjectBenchmarks, DSoftPublishObjectBenchmarks, MediatRSendObjectBenchmarks, MediatRPublishObjectBenchmarks, DispatchRPublishObjectBenchmarks, MediatorSGSendObjectBenchmarks, MediatorSGPublishObjectBenchmarks) measuring runtime-typed dispatch overhead vs generic dispatch.DSoftTypedExtensionVsMediatorSGBenchmarks class comparing DSoft source-generated typed extensions against martinothamar/Mediator SG dispatch in the same isolated process.Packaging/BuildTransitivePropsTests verifying that both build/ and buildTransitive/ props files contain InterceptorsNamespaces, InterceptorsPreviewNamespaces, DSoftMediatorSuppressInterceptors, and stay in sync. New Packaging/InterceptorNamespaceCompilationTests using Roslyn in-memory compilation to reproduce and guard against CS9137 regressions.buildTransitive/DSoftStudio.Mediator.props file was missing InterceptorsNamespaces / InterceptorsPreviewNamespaces configuration. When NuGet sees both build/ and buildTransitive/ with the same filename, buildTransitive/ takes priority even for direct PackageReference consumers. The source generator emitted interceptors but the compiler rejected them because the namespace was not enabled. The transitive props file now mirrors build/ with full interceptor namespace configuration and DSoftMediatorSuppressInterceptors support.FakeOrderRepository singleton registration to MediatorSGHelper.AddMediatorSG() to prevent the eager handler resolution from failing during realistic pipeline benchmarks.InterceptorHelpers dispatch body builder — AppendSendDispatchBody extracted to a single static method shared by SendInterceptorGenerator (interceptors) and MediatorExtensionsGenerator (typed extensions). Ensures dispatch logic is defined once, with isRelease parameter for branchless castclass (Release interceptors) vs isinst + virtual-dispatch fallback (typed extensions and Debug interceptors).MediatorExtensionsGenerator now emits isinst + virtual-dispatch fallback (never branchless castclass), ensuring typed extensions work correctly in test projects, mocking scenarios, and dotnet test -c Release CI pipelines regardless of interceptor suppression.ColdStartBenchmarks, ConcurrencyBenchmarks, PublishBenchmarks, SendBenchmarks, SendNoBehaviorsBenchmarks, StreamBenchmarks) and run-all-benchmarks.cmd. Each library now has isolated per-category benchmark classes under Benchmarks/{Library}/ directories.generate-benchmarks-md.ps1 now filters Direct-only rows from “All Libraries” combined sections, removes empty separator rows, and adds library-group separators with dynamic column-width matching for cleaner combined tables.run-*.cmd files updated to include realistic pipeline and Send(object)/Publish(object) benchmark classes.DSoftStudio.Mediator.FluentValidation, DSoftStudio.Mediator.HybridCache, DSoftStudio.Mediator.OpenTelemetry updated to depend on DSoftStudio.Mediator >= 1.1.7.NativeAotSafetyTests suite (7 tests) validating that PrecompilePipelines() correctly replaces open-generic IPipelineBehavior<,> descriptors with closed-generic versions. Covers end-to-end dispatch (Unit/int/bool), multiple behaviors ordering, zero open-generic assertion, lifetime preservation (Transient/Scoped/Singleton), and idempotency.MockDetectionAnalyzer — The DetectMockingLibrary call site now declares string? and uses is null pattern matching, eliminating the CS8600 warning without sentinel values.MockDetectionAnalyzer — DetectMockingLibrary return type corrected to string? to match its nullable contract.ReferencedAssemblyScanner — GetAllExternalHandlers parameter changed to List<SkippedHandlerInfo>? to accurately reflect its optional nature.MockDetectionAnalyzerTests — TryGetValue overrides now match the base AnalyzerConfigOptions signature (out string value) using null! for the false-return pattern.CancellationToken.None / omitted CancellationToken arguments in test methods replaced with TestContext.Current.CancellationToken for responsive test cancellation under xUnit v3.dotnet publish AOT + native binary execution..dtbcache.v2 causing IntelliSense false positives (CS0246/CS0518) documented and resolved via cache invalidation.DSoftStudio.Mediator.FluentValidation, DSoftStudio.Mediator.HybridCache, DSoftStudio.Mediator.OpenTelemetry updated to depend on DSoftStudio.Mediator >= 1.1.6.DSoftStudio.Mediator.Abstractions NuGet package — Contracts (interfaces and base types) are now published as a separate package. Domain, application-core, and test projects can reference only DSoftStudio.Mediator.Abstractions to get ISender, IPublisher, IMediator, IRequest<T>, INotification, and related abstractions without pulling in the runtime or source generators. This is the recommended pattern for unit-testing with mocking frameworks (Moq, NSubstitute, etc.) since no interceptors are active.DSoftStudio.Mediator.Abstractions in test projects for clean mock isolation.DSoftMediatorSuppressInterceptors MSBuild kill switch — Set <DSoftMediatorSuppressInterceptors>true</DSoftMediatorSuppressInterceptors> in a project to completely disable interceptor generation. Useful for test projects or environments where interceptors are undesirable.NotificationPublisherFlag — Write-once volatile flag that allows the runtime Publish path to skip unnecessary overhead when no notification publishers are registered.samples/cross-project-mocking/ demonstrates the recommended architecture: Host (runtime + generators), Host.Application (abstractions only), Host.Tests (mocks against abstractions).public or add an InternalsVisibleTo attribute.DSoftStudio.Mediator.ModularMonolith.Tests) with compile-time guard (WarningsAsErrors=CS0122) ensuring the internal-handler accessibility fix cannot regress.DSoftMediatorSuppressInterceptors=true was silently ignored when the project consumed DSoftStudio.Mediator transitively (e.g. Host.Tests ? Host via ProjectReference). Root cause: the CompilerVisibleProperty declaration lived only in the build/ NuGet folder, which is not transitive. Added a buildTransitive/DSoftStudio.Mediator.props file containing just the CompilerVisibleProperty declaration so the source generator can read the suppress flag in any downstream project. Interceptor namespaces are intentionally not included in the transitive props.SendInterceptorGenerator, PublishInterceptorGenerator, StreamInterceptorGenerator) no longer rewrite Send, Publish, or CreateStream calls that appear inside expression tree lambdas (e.g. Moq Setup() / Verify()). A new IsInsideExpressionTreeLambda helper walks the syntax tree and checks the lambda’s ConvertedType against System.Linq.Expressions.Expression<T>, skipping those call sites. Direct invocations outside expression trees continue to be intercepted as before. (#2)ParallelNotificationPublisherTests and PipelineGcLeakTests stabilized with deterministic synchronization to eliminate intermittent CI failures.internal handlers discovered in referenced assemblies, which previously caused CS0122 at compile time. Accessibility is now validated during generation, and InternalsVisibleTo is respected. A new DSOFT005 warning identifies every skipped handler.OptimizationLevel-conditional code: Release builds use castclass (branchless, ~0.36 ns saving via GDV), Debug builds use isinst + null-check for mock-framework compatibility. Hot-path Send is now 1.05× vs raw handler.Publish dispatch leverages NotificationPublisherFlag to skip publisher resolution when none are registered. Overhead dropped from 2.19× to 1.07–1.11× vs raw handler.DSoftStudio.Mediator now declares a public NuGet dependency on DSoftStudio.Mediator.Abstractions (previously the Abstractions DLL was embedded with PrivateAssets="all"). Companion packages (FluentValidation, HybridCache, OpenTelemetry) receive Abstractions transitively through DSoftStudio.Mediator.publish job uses needs: build-and-test, guaranteeing that no package is published if any test fails. GitHub Packages receives packages on every push to main; NuGet.org only on version tags (v*).DSoftStudio.Mediator.FluentValidation (FluentValidation 12.1.1), DSoftStudio.Mediator.HybridCache (HybridCache 10.4.0), DSoftStudio.Mediator.OpenTelemetry (OpenTelemetry 1.15.0) updated with latest dependency versions.netstandard2.0 only — Removed net8.0 multi-target; netstandard2.0 provides maximum compatibility across all .NET versions.xunit.runner.visualstudio 3.1.5 and Microsoft.NET.Test.Sdk 18.3.0.InternalsVisibleTo — Source-generated types no longer conflict when a test project (or any referencing project) has InternalsVisibleTo access to the host project. (#1)
file modifier, making them invisible across assemblies.DSoftStudio.Mediator.Generated.{AssemblyName}) with a global using for transparent usage.[assembly: MediatorHandlerRegistration] attributes, replacing the previous PE metadata scanning approach that could miss internal members.Send(object) namespace shadowing — The runtime Send(object) extension is now generated alongside typed extensions in the per-assembly namespace, preventing C# resolution from shadowing typed overloads.Send(object) is now fully source-generated — removed SenderObjectExtensions.cs from the runtime DLL.https://docs.dsoftstudio.com/mediator.docs/, samples/, and benchmarks/ from analysis to prevent false-positive bugs on non-production code.<picture> HTML tag with pure Markdown image syntax () across all package READMEs. NuGet does not support <picture> or <p align="center"> HTML tags, causing raw HTML to render on package pages.Send(object) dispatch fails when multiple ServiceProvider instances coexist —
The Send(object) runtime dispatch delegate referenced the static
RequestDispatch<TRequest, TResponse>.Pipeline field, which is write-once
(Interlocked.CompareExchange). When parallel test classes (or multi-tenant hosts)
created separate ServiceProvider instances with different pipeline configurations,
the first registration won the static slot. Subsequent providers that lacked
PipelineChainHandler registrations threw InvalidOperationException.
The delegate now resolves directly from the passed-in IServiceProvider via
GetService<PipelineChainHandler<TRequest, TResponse>>() (nullable probe) with
fallback to GetRequiredService<IRequestHandler<TRequest, TResponse>>(),
making it independent of static initialization order.Self-handling requests — request classes (or records) that implement IRequest<T>,
ICommand<T>, or IQuery<T> and contain a static Execute method are automatically
discovered at compile time and wired into the mediator pipeline. No separate handler
class is required. The source generator emits an internal adapter that bridges the
static method to IRequestHandler<TRequest, TResponse>, preserving the same
zero-overhead dispatch path (HandlerCache, pipeline behaviors, typed extensions,
and handler validation).
Supported return types: T (sync), Task<T>, ValueTask<T>, void (Unit),
Task (async Unit).
DI injection: service parameters in the Execute signature are resolved from DI
automatically. Stateless self-handlers (no DI services) are registered as Singleton;
with DI dependencies as Transient.
Full pipeline integration: behaviors, pre/post processors, exception handlers,
typed Send() extensions, and ValidateMediatorHandlers() all work with
self-handling requests.
Fail-fast handler validation — new source-generated ValidateMediatorHandlers()
extension method on IServiceProvider. Resolves every mediator handler from DI at
startup and throws an AggregateException with all failures if any handler is
misconfigured. Detects missing registrations, broken constructor dependencies, and
incomplete pipeline configurations before the first request is processed.
var app = builder.Build();
app.Services.ValidateMediatorHandlers(); // throws AggregateException if misconfigured
DSOFT002: Duplicate request handler — compile-time diagnostic (Warning) when
multiple IRequestHandler<TRequest, TResponse> implementations are found for the
same <TRequest, TResponse> pair. With Microsoft.Extensions.DI, only the last
registration is resolved via GetRequiredService<T>() — earlier handlers are
silently ignored. The diagnostic lists all conflicting implementations.
DSOFT003: Duplicate stream handler — compile-time diagnostic (Warning) when
multiple IStreamRequestHandler<TRequest, TResponse> implementations are found for
the same <TRequest, TResponse> pair. Same root cause as DSOFT002.
Runtime-typed Send(object) dispatch — new Send(this ISender, object, CancellationToken)
extension method for message bus / command queue scenarios where the consumer only has
an object reference at runtime. Uses a compile-time generated
FrozenDictionary<Type, DispatchDelegate> dispatch table (same architecture as
Publish(object)) — no reflection, no MakeGenericType, fully AOT-safe.
The extension method design preserves overload resolution: generated typed extensions
(e.g. Send(this ISender, Ping)) are always preferred when the compile-time type is
known. Send(object) is only selected when the argument is typed as object.
Zero impact on the existing Send<TRequest, TResponse>() hot path — completely
separate dispatch table and code path.
See ADR-0004 for design rationale.
DSoftStudio.Mediator.OpenTelemetry package — New companion NuGet package providing
automatic distributed tracing and metrics for all mediator operations via standard
IPipelineBehavior<,>, IStreamPipelineBehavior<,>, and an INotificationPublisher
decorator — zero changes to the core mediator library.
Tracing: Single ActivitySource("DSoftStudio.Mediator") with span names following
{TypeName} {kind} convention (e.g. CreateUser command, GetUsers query).
Span attributes include mediator.request.type, mediator.response.type, and
mediator.request.kind (command/query/request/notification/stream).
Exception recording with configurable stack traces.
Metrics: Single Meter("DSoftStudio.Mediator") with three instruments:
mediator.request.duration (histogram, seconds), mediator.request.active
(up-down counter), mediator.request.errors (counter with error.type tag).
Notification instrumentation: InstrumentedNotificationPublisher decorator
creates a parent span per Publish() call with per-handler child spans — unique
among .NET mediator libraries.
Zero-cost when unused: HasListeners() / Instrument.Enabled short-circuits
add ~1 ns when no OTel exporter is configured.
Configuration: AddMediatorInstrumentation() with options for filtering
(suppress health checks), enrichment (custom tags), and independent tracing/metrics
toggles.
See ADR-0005 for design rationale.
DSoftStudio.Mediator.FluentValidation package — New companion NuGet package
providing automatic request validation via FluentValidation. Registers a single
open-generic ValidationBehavior<TRequest, TResponse> pipeline behavior that
resolves all IValidator<TRequest> instances from DI, runs validation before the
handler, and throws MediatorValidationException on failure.
Key features:
MediatorValidationException.ErrorsByProperty for easy ValidationProblemDetails mappingservices.AddMediatorFluentValidation()DSoftStudio.Mediator.HybridCache package — New companion NuGet package
providing automatic query/request caching via Microsoft’s HybridCache
(Microsoft.Extensions.Caching.Hybrid). Registers a single open-generic
CachingBehavior<TRequest, TResponse> pipeline behavior that checks if the
request implements ICachedRequest and caches results via HybridCache.GetOrCreateAsync().
Key features:
HybridCacheICachedRequest marker interface with CacheKey and Duration (default: 60s)ICachedRequestservices.AddMediatorHybridCache()HandlerInfo struct in DependencyInjectionGenerator refactored to use
C# primary constructor (IDE0290).ADR-0004: Runtime-Typed Send(object) Dispatch — Accepted. Adds Send(object)
as an extension method (not interface method) using a compile-time generated
FrozenDictionary dispatch table. Extension method design is required because
ISender.Send<TRequest, TResponse> has two generic type parameters that cannot be
inferred — an instance Send(object) would shadow all generated typed extensions
due to C# overload resolution rules. See docs/mediator/adr/0004-runtime-typed-send.md.
ADR-0005: OpenTelemetry Instrumentation Package — Accepted. Separate NuGet
package (DSoftStudio.Mediator.OpenTelemetry) providing automatic distributed
tracing and metrics via standard pipeline behaviors, with zero impact on the core
mediator library. See docs/mediator/adr/0005-opentelemetry-instrumentation.md.
MediatorPipelineGenerator now checks IsGenericTypeDefinition for IPipelineBehavior<,>, IRequestPreProcessor<>, IRequestPostProcessor<,>, and IRequestExceptionHandler<,>, fixing a bug where behaviors registered as open generics were silently skipped.IStreamRequestHandler<TRequest, TResponse> covariance — TResponse changed from invariant to out to match the IStreamRequest<out TResponse> contract.PipelineChainCache<TRequest, TResponse> and StreamPipelineChainCache<TRequest, TResponse> cache Scoped/Singleton chains per-thread, eliminating a GetService call on the hot path. Transient chains continue resolving fresh each call.HandlerCache<TRequest, TResponse> replaces GetRequiredService on every Send() with a cached resolution.StreamPipelineChainHandler now pre-links the behavior chain at construction (like PipelineChainHandler), removing mutable state (_behaviorIndex, _active, Interlocked) from the hot path.SequentialNotificationPublisher optimized — Materialize handlers to array once; index-based for loop with IsCompletedSuccessfully short-circuit; AwaitRemaining resumes from currentIndex + 1 instead of re-scanning with ReferenceEquals.IsPipelineChainCacheable / IsStreamChainCacheable — New Volatile.Read/Volatile.Write static flags in RequestDispatch<T,R> and StreamDispatch<T,R> for zero-cost cache-vs-resolve branching.MakeGenericType + Expression.Compile from Publish(object) — The NotificationHandlerWrapper / NotificationHandlerWrapperImpl<T> pattern (runtime reflection) replaced with NotificationObjectDispatch, a compile-time generated dispatch table. Fully AOT/trimmer-safe.NotificationDispatcher — Replaced with NotificationCachedDispatcher (compile-time dispatch with handler caching).NotificationHandlerWrapper / NotificationHandlerWrapperImpl<T> — No longer needed; AOT dispatch table handles all scenarios.IServiceProviderAccessor from Abstractions to core — Interceptor-internal interface no longer exposed in the public Abstractions assembly.IsTrimmable and conditional IsAotCompatible to the Abstractions csproj.CancellationToken moved to last parameter in PipelineChainHandler.AwaitPostProcessorAndContinue, SequentialNotificationPublisher.AwaitRemaining, NotificationCachedDispatcher.PublishTests and NotificationWrapperTests..Result calls with await in tests.InterceptorHelpers (shared ImplementsInterface, ResolveRequestParameter), refactored ReferencedAssemblyScanner.CollectHandlersFromAssembly, extracted TryResolveInferredTypes in SendInterceptorGenerator, extracted PipelineChainHandler.ComputePipelineMode.Unit operators — Added <, >, <=, >= comparison operators (CA1036).AllocationRegressionTests and ThroughputRegressionTests with CI-safe thresholds (Send = 50 µs, Publish = 50 µs, Stream = 100 µs; Send = 128 B, Publish = 64 B, Stream = 512 B).generate-benchmarks-md.ps1 with isolated vs. combined run variance note..github/workflows/sonar.yml with Coverlet/OpenCover coverage, sample/benchmark exclusions.StreamGenerator registers StreamPipelineChainHandler as Singleton/Scoped/Transient based on component lifetimes.