Skip to main content

RA.Utilities.Feature

· 7 min read
Redon Alla
.NET, React, Angular Developer

Version 10.2.0​

Date Badge NuGet version

💥 Breaking Changes​

  • Result removed from the mediator contract. Handlers now return Task<TResponse> (or Task for void requests), and IMediator.Send returns Task<TResponse> / Task. Failures surface as exceptions: handlers throw typed exceptions (NotFoundException, BadRequestException, and the rest of the RA.Utilities.Core.Exceptions hierarchy), and ValidationBehavior throws a BadRequestException carrying the mapped ValidationError collection when validation fails. The Result monad remains available in RA.Utilities.Core for callers that still want it.
  • RequestHandler base classes simplified. The explicit interface implementations are now plain passthroughs with no exception wrapping; unhandled handler exceptions propagate to the caller (the API layer's GlobalExceptionHandler converts them to HTTP responses).

✨ New Features​

  • Source-generated IMediator implementation (MediatorImpl). The source generator now emits a complete IMediator implementation into every assembly referencing the package, and AddMediator() registers it as the IMediator implementation automatically — opt out with new MediatorOptions { UseGeneratedMediator = false }.
    • Direct pipeline injection. The generated Send knows each message's handler and pipeline behaviors, resolves them by concrete type, and calls them directly: no GetRequiredService<IRequestHandler<...>> interface resolution, no GetServices enumeration, no as 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 new FEAG010 warning and remain available through explicit AddDecoration registration under the runtime mediator.
    • Interface dispatch devirtualizes through the base class. MediatorImpl derives from the runtime Mediator, so IMediator-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) use typeof chains in small projects and switch to static Dictionary<Type, Delegate> lookups above a project-size threshold (more than 8 messages).
  • Object-based dispatch. Send(object) and Publish(object) overloads on both the generated and the runtime Mediator. 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 throw RA.Utilities.Feature.Exceptions.HandlerNotFoundException.
  • New message and handler diagnostics (category RA.Utilities.Feature.Generators):
    • FEAG006 (Error) — message implements multiple message contracts (two IRequest<TResponse> interfaces, or a request contract together with INotification); 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 both IRequestHandler<TRequest> and IRequestHandler<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):

ScenarioRuntime mediatorGenerated MediatorImpl (concrete)MediatR
Send, 0 behaviors169 ns / 632 B132 ns / 464 B168 ns / 568 B
Send, 3 behaviors265 ns / 1160 B195 ns / 880 B251 ns / 1096 B
Send, void157 ns / 536 B118 ns / 368 B149 ns / 472 B
Send, with context235 ns / 920 B179 ns / 720 B176 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​

Date Badge NuGet version

💥 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.CorrelationId instead of ctx.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-defined T carries context data. Each Send/Publish call gets an isolated instance. No dictionaries, no magic strings, no boxing.
  • Context-aware IMediator overloads — Send<TRequest, TResponse, TContext>(), Send<TRequest, TContext>(), and Publish<TNotification, TContext>().
  • Generic context methods on all pipeline interfaces — IPipelineBehavior, INotificationBehavior, IRequestHandler, and INotificationHandler now have generic HandleAsync<TContext>(...) overloads with default implementations for full backward compatibility.
  • RequestHandler base classes — new HandleAsync<TContext>(...) protected virtual method. Derived handlers can override to consume typed context data; default delegates to the non-context abstract method.
  • RequestHandler<TRequest> and RequestHandler<TRequest, TResponse> abstract base classes — provide built-in ILogger-based logging for request start/finish and automatic exception-to-Result.Failure conversion. Derived classes override HandleAsync and focus solely on business logic.
  • Configurable NotificationRetryBehavior — now accepts maxRetries (default: 3) and baseDelayMilliseconds (default: 200) via its constructor, with ArgumentOutOfRangeException validation.

🐛 Bug Fixes​

  • Mediator — Removed unreachable null-coalescing ?? throw after GetRequiredService<T>() in both Send overloads.
  • Mediator — Publish now isolates each notification handler in its own try-catch. One failing handler no longer prevents subsequent handlers from executing.
  • Mediator — Publish returns immediately when no handlers are registered for a notification type, skipping unnecessary behavior resolution.
  • NotificationMetricsBehavior — Replaced the Stopwatch instance field with a local Stopwatch.StartNew() variable, eliminating a thread-safety hazard.
  • ValidationBehavior — Both constructors now guard against null with ?? Array.Empty<IValidator<TRequest>>().
  • LoggingBehavior<TRequest> — The no-response variant now logs consistently with the two-param variant ("Start. Request: {...}" / "Finished. Result: {...}").
  • IMediator XML 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/, and Models/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​

Date Badge NuGet version

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​

Date Badge NuGet version

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​

Date Badge NuGet version

Initial release candidate introducing the custom mediator and CQRS pipeline foundation for the RA.Utilities.Feature package.