RA.Utilities.Dispatcher
The main goal of this package is to help you organize your application's business logic into self-contained "features" or "slices." Instead of having logic for a single operation scattered across multiple layers (e.g., a controller, a service, a repository), all the code for one use case—from the incoming request to the final response—is located together.
This solves a common problem with traditional layered architectures where classes can become bloated and tightly coupled, making the codebase difficult to navigate and maintain.
✨ Key Features and How They Help
The package provides several key components to achieve this clean architecture:
- Source-Generated Dispatcher (
IDispatcher):
- Purpose: A lightweight, reflection-free mediator — no external MediatR dependency. The
IDispatcherimplementation itself is emitted by a source generator shipped inside the package and registered byAddMediator(). - Benefit: Strongly-typed dispatch for both request/response (
Send) and publish/subscribe (Publish) patterns. InjectIDispatcheror the concrete generatedMediatorfor the fastest path — see Generated Mediator.
- Result-Based Dispatch (
Resultmonad):
- Purpose: Handlers and
IDispatcherreturnValueTask<Result<TResponse>>/ValueTask<Result>. Expected failures are returned asResult.Failureinstead of being thrown. - Benefit: Failure is an explicit part of the contract — no hidden control flow, no try/catch at call sites. The
Resultmonad comes fromRA.Utilities.Core.Resultsand is chainable withMap,Bind,Match,OnSuccess,OnFailure.
- Zero-Configuration Handler Registration (
Auto Registration):
- Purpose: A source generator shipped in the package discovers all
IRequestHandler<,>/IRequestHandler<>/INotificationHandler<>implementations and closed pipeline behaviors, and registers them whenAddMediator()is called. - Benefit: Plain handlers and closed behaviors need no explicit
AddDispatcher/AddNotification/AddDecorationcalls — only validators and generic behaviors still require explicit registration.
- Base Handlers (
RequestHandler<TRequest, TResponse>):
- Purpose: Abstract base classes for your CQRS command and query handlers.
- Benefit: They remove boilerplate: implement the single abstract
HandleAsyncmethod and returnResultvalues directly — no explicit interface plumbing.
- Validation Pipeline Behavior (
ValidationBehavior<TRequest, TResponse>):
- Purpose: A pipeline behavior that automatically validates incoming commands and queries before they reach your business logic.
- Benefit: Uses
FluentValidation. If validation fails, it short-circuits by returning a failed result containing aBadRequestExceptioncarrying the structured validation errors. Your handlers only ever deal with valid data.
- Notification System (
INotification):
- Purpose: Publish/subscribe eventing — notifications handled by zero or more subscribers, each wrapped in its own behavior pipeline.
- Benefit: Built-in notification behaviors for logging, metrics, and retry.
Publishruns every handler and aggregates failures into the returnedResult— one failing handler doesn't prevent others from executing.
- Typed Pipeline Context (
PipelineContext<T>):
- Purpose: Pass strongly-typed data through the entire pipeline — no dictionaries, no magic strings, no boxing.
- Benefit: Behaviors and handlers read/write properties on a user-defined
T. Each call gets its own isolated instance. Fully opt-in — existingSend/Publishcalls compile unchanged.
- Fluent Validation Integration (
AddValidator):
- Purpose:
.AddValidator<TValidator>()registers the validator and wires theValidationBehaviorinto the pipeline in one call. - Benefit:
ValidationBehaviorruns all registeredIValidator<TRequest>implementations; failures never reach the handler and the caller receives a failedResultcarrying aBadRequestExceptionto map to a400 Bad Request.
- Benchmarked against MediatR (
Benchmarks):
- Purpose: A BenchmarkDotNet harness comparing the generated mediator against MediatR 14.2 at equal scoped lifetimes, scenario for scenario.
- Benefit: Published numbers for the send and publish shapes — the concrete mediator is 12–39% faster with 11–31% fewer allocations — plus the harness to reproduce them locally.
🌟 Benefits
The package has been fully rewritten to avoid reflection; handlers are discovered by a source generator and resolved through strongly-typed generic DI registration instead of dynamic type lookups or runtime assembly scanning.
- ✅ No runtime reflection
- ✅ Purely generic and compile-time safe
- ✅ Zero-config handler registration via the in-package source generator — plain handlers need no registration code
- ✅ Result-based dispatch — expected failures are returned as
Result.Failure, never thrown - ✅ Simplified DI registration via fluent builders (
DispatcherBuilderExtensions) for behaviors and validators - ✅ Custom dispatcher supports both request/response and publish/subscribe
- ✅ Multiple notification handlers per notification type
- ✅ Multiple notification pipeline behaviors (logging, metrics, retry)
- ✅ Fluent builder style consistent across requests and notifications
- ✅ Abstract
RequestHandlerbase classes — oneHandleAsyncoverride per handler - ✅ Typed
PipelineContext<T>for passing data through the pipeline — no dictionaries
🔧 Design Overview
| Namespace | Purpose |
|---|---|
Abstractions | Core interfaces: IDispatcher, IRequest, IRequestHandler, INotification, IPipelineBehavior, INotificationBehavior |
Handlers | Abstract base classes: RequestHandler<TRequest>, RequestHandler<TRequest, TResponse> |
Behaviors | Built-in pipeline and notification behaviors: logging, validation, metrics, retry |
Builders | Fluent builders: DispatcherBuilder<TRequest[, TResponse]>, NotificationDispatcherBuilder<TNotification> |
Extensions | DI registration extensions: AddMediator(), AddDispatcher(), AddNotification() |
Models | Delegates: RequestHandlerDelegate, NotificationHandlerDelegate; context carrier PipelineContext<T> |
Generated | Registration queues fed by the in-package source generator |
Exceptions | HandlerNotFoundException — typed failure for undispatchable object-based messages |
🛠️ Installation
You can install the package via the .NET CLI:
dotnet add package RA.Utilities.Dispatcher
Or through the NuGet Package Manager console:
Install-Package RA.Utilities.Dispatcher
🔗 Dependencies
RA.Utilities.Core.ResultsRA.Utilities.Core.ExceptionsRA.Utilities.Application.ValidationFluentValidationMicrosoft.Extensions.DependencyInjection.AbstractionsMicrosoft.Extensions.Logging.Abstractions
Abstractions
9 items
Behaviors
5 items
Models
3 items
Handlers
1 item
Generated Mediator
The source generator in the RA.Utilities.Dispatcher package generates the IDispatcher implementation itself — the generated Mediator is the only dispatcher in the package.
Builders
2 items
Extensions
2 items
Exceptions
1 item
Benchmarks
The RA.Utilities.Dispatcher.Benchmarks project compares the generated mediator against MediatR 14.2 using BenchmarkDotNet, scenario for scenario. The tables below are the current dataset, collected in a single session on 2026-10-18.
Generated
2 items
Auto Registration
Handlers do not need to be registered one by one.
Troubleshooting & FAQ
Common Issues
Migration Guides
RA.Utilities.Feature v10.x → RA.Utilities.Dispatcher v10.0.0