RA.Utilities.Feature
Version 10.2.0
💥 Breaking Changes
Resultremoved from the mediator contract. Handlers now returnTask<TResponse>(orTaskfor void requests), andIMediator.SendreturnsTask<TResponse>/Task. Failures surface as exceptions: handlers throw typed exceptions (NotFoundException,BadRequestException, and the rest of theRA.Utilities.Core.Exceptionshierarchy), andValidationBehaviorthrows aBadRequestExceptioncarrying the mappedValidationErrorcollection when validation fails. TheResultmonad remains available inRA.Utilities.Corefor callers that still want it.RequestHandlerbase classes simplified. The explicit interface implementations are now plain passthroughs with no exception wrapping; unhandled handler exceptions propagate to the caller (the API layer'sGlobalExceptionHandlerconverts them to HTTP responses).
✨ New Features
- Source-generated
IMediatorimplementation (MediatorImpl). The source generator now emits a completeIMediatorimplementation into every assembly referencing the package, andAddMediator()registers it as theIMediatorimplementation automatically — opt out withnew MediatorOptions { UseGeneratedMediator = false }.- Direct pipeline injection. The generated
Sendknows each message's handler and pipeline behaviors, resolves them by concrete type, and calls them directly: noGetRequiredService<IRequestHandler<...>>interface resolution, noGetServicesenumeration, noas T[]casts, and no delegate chain when the pipeline is empty. - Closed behaviors auto-register. Non-generic
IPipelineBehavior<,>/IPipelineBehavior<>/INotificationBehavior<>implementations are auto-registered as their concrete types and injected directly in deterministic order. Generic behaviors report the newFEAG010warning and remain available through explicitAddDecorationregistration under the runtime mediator. - Interface dispatch devirtualizes through the base class.
MediatorImplderives from the runtimeMediator, soIMediator-typed calls keep the familiar dispatch and allocations, and messages the generated code cannot cover fall back to the inherited runtime implementation. - Fast dictionary lookups for object dispatch.
Send(object)/Publish(object)usetypeofchains in small projects and switch to staticDictionary<Type, Delegate>lookups above a project-size threshold (more than 8 messages).
- Direct pipeline injection. The generated
- Object-based dispatch.
Send(object)andPublish(object)overloads on both the generated and the runtimeMediator. The runtime implementation maps each message type to its closed handler contract once (cached in conditional weak tables), with no reflection after the first dispatch; unknown message types throwRA.Utilities.Feature.Exceptions.HandlerNotFoundException. - New message and handler diagnostics (category
RA.Utilities.Feature.Generators):FEAG006(Error) — message implements multiple message contracts (twoIRequest<TResponse>interfaces, or a request contract together withINotification); excluded from generated dispatch.FEAG007(Warning) — request message has no handler in the compilation.FEAG008(Warning) — generic or inaccessible message falls back to runtime dispatch.FEAG009(Warning) — handler implements bothIRequestHandler<TRequest>andIRequestHandler<TRequest, TResponse>for the same message.FEAG010(Warning) — generic pipeline behavior cannot be injected by the generated mediator.
MediatorOptions— the single switch between the generated mediator (default) and the runtime mediator.
⚡ Performance
Benchmarks (MediatR handlers registered scoped, matching this package's lifetime; Apple M1, .NET 10):
| Scenario | Runtime mediator | Generated MediatorImpl (concrete) | MediatR |
|---|---|---|---|
| Send, 0 behaviors | 169 ns / 632 B | 132 ns / 464 B | 168 ns / 568 B |
| Send, 3 behaviors | 265 ns / 1160 B | 195 ns / 880 B | 251 ns / 1096 B |
| Send, void | 157 ns / 536 B | 118 ns / 368 B | 149 ns / 472 B |
| Send, with context | 235 ns / 920 B | 179 ns / 720 B | 176 ns / 624 B |
The generated mediator is 21-22% faster than MediatR with 18-20% fewer allocations in every comparable send shape, and matches it in the context shape (where the extra allocation is the PipelineContext<T> MediatR does not have).
Version 10.1.0
💥 Breaking Change: PipelineContext is now generic PipelineContext<T>
The dictionary-based PipelineContext class (with Set<T>()/Get<T>()) has been replaced with a strongly-typed PipelineContext<T> where T is a user-defined class with a parameterless constructor.
Why:
- Compile-time safety — typos in context keys are caught by the compiler instead of silently returning
default - No boxing — value types don't need to be boxed/unboxed
- IntelliSense — context properties are discoverable via autocompletion in any IDE
- Simpler API —
ctx.Data.CorrelationIdinstead ofctx.Get<string>("correlationId")
Migration from v10.0.1: Define a class for your context data, use PipelineContext<MyContext> instead of PipelineContext, and access properties via .Data. Existing Send/Publish calls without a context type parameter continue to compile unchanged — the context type parameter is entirely opt-in. See the migration guide for detailed before/after examples.
✨ New Features
- Typed
PipelineContext<T>— strongly-typed data carrier flowing through the entire pipeline. User-definedTcarries context data. EachSend/Publishcall gets an isolated instance. No dictionaries, no magic strings, no boxing. - Context-aware
IMediatoroverloads —Send<TRequest, TResponse, TContext>(),Send<TRequest, TContext>(), andPublish<TNotification, TContext>(). - Generic context methods on all pipeline interfaces —
IPipelineBehavior,INotificationBehavior,IRequestHandler, andINotificationHandlernow have genericHandleAsync<TContext>(...)overloads with default implementations for full backward compatibility. RequestHandlerbase classes — newHandleAsync<TContext>(...)protected virtual method. Derived handlers can override to consume typed context data; default delegates to the non-context abstract method.RequestHandler<TRequest>andRequestHandler<TRequest, TResponse>abstract base classes — provide built-inILogger-based logging for request start/finish and automatic exception-to-Result.Failureconversion. Derived classes overrideHandleAsyncand focus solely on business logic.- Configurable
NotificationRetryBehavior— now acceptsmaxRetries(default: 3) andbaseDelayMilliseconds(default: 200) via its constructor, withArgumentOutOfRangeExceptionvalidation.
🐛 Bug Fixes
- Mediator — Removed unreachable null-coalescing
?? throwafterGetRequiredService<T>()in bothSendoverloads. - Mediator —
Publishnow isolates each notification handler in its owntry-catch. One failing handler no longer prevents subsequent handlers from executing. - Mediator —
Publishreturns immediately when no handlers are registered for a notification type, skipping unnecessary behavior resolution. NotificationMetricsBehavior— Replaced theStopwatchinstance field with a localStopwatch.StartNew()variable, eliminating a thread-safety hazard.ValidationBehavior— Both constructors now guard againstnullwith?? Array.Empty<IValidator<TRequest>>().LoggingBehavior<TRequest>— The no-response variant now logs consistently with the two-param variant ("Start. Request: {...}"/"Finished. Result: {...}").IMediatorXML doc — Fixed typo:"reques"→"request".- All built-in behaviors now pass the typed
PipelineContext<T>through the delegate chain.
📖 Documentation
- Comprehensive README rewrite covering the custom mediator, base handlers, pipeline behaviors, notification system, typed pipeline context, and all built-in behaviors.
- New Docusaurus sections:
Handlers/,Builders/, andModels/PipelineContext. - Fixed stale code samples throughout — removed MediatR references, corrected generic arity, and updated registration examples to use the fluent builder API.
- New migration guide covering v10.0.1 → v10.1.0 with detailed before/after examples.
- New troubleshooting entries for common context-related issues.
Version 10.0.1
Add MustMatchesCurrencyFormat extension method. This design allows developers to chain this custom validation rule into any string property within their FluentValidation validator classes.
Version 10.0.0
This release marked the transition from release candidate to stable, providing the custom mediator, pipeline behavior system, notification system with behaviors, and the fluent builder registration API.
Version 10.0.0-rc.2
Initial release candidate introducing the custom mediator and CQRS pipeline foundation for the RA.Utilities.Feature package.