Skip to main content

RA.Utilities.Dispatcher

NuGet version NuGet Downloads Codecov Publish NuGet GitHub license

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:

  1. Source-Generated Dispatcher (IDispatcher):
  • Purpose: A lightweight, reflection-free mediator — no external MediatR dependency. The IDispatcher implementation itself is emitted by a source generator shipped inside the package and registered by AddMediator().
  • Benefit: Strongly-typed dispatch for both request/response (Send) and publish/subscribe (Publish) patterns. Inject IDispatcher or the concrete generated Mediator for the fastest path — see Generated Mediator.
  1. Result-Based Dispatch (Result monad):
  • Purpose: Handlers and IDispatcher return ValueTask<Result<TResponse>> / ValueTask<Result>. Expected failures are returned as Result.Failure instead of being thrown.
  • Benefit: Failure is an explicit part of the contract — no hidden control flow, no try/catch at call sites. The Result monad comes from RA.Utilities.Core.Results and is chainable with Map, Bind, Match, OnSuccess, OnFailure.
  1. 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 when AddMediator() is called.
  • Benefit: Plain handlers and closed behaviors need no explicit AddDispatcher / AddNotification / AddDecoration calls — only validators and generic behaviors still require explicit registration.
  1. Base Handlers (RequestHandler<TRequest, TResponse>):
  • Purpose: Abstract base classes for your CQRS command and query handlers.
  • Benefit: They remove boilerplate: implement the single abstract HandleAsync method and return Result values directly — no explicit interface plumbing.
  1. 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 a BadRequestException carrying the structured validation errors. Your handlers only ever deal with valid data.
  1. 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. Publish runs every handler and aggregates failures into the returned Result — one failing handler doesn't prevent others from executing.
  1. 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 — existing Send/Publish calls compile unchanged.
  1. Fluent Validation Integration (AddValidator):
  • Purpose: .AddValidator<TValidator>() registers the validator and wires the ValidationBehavior into the pipeline in one call.
  • Benefit: ValidationBehavior runs all registered IValidator<TRequest> implementations; failures never reach the handler and the caller receives a failed Result carrying a BadRequestException to map to a 400 Bad Request.
  1. 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 RequestHandler base classes — one HandleAsync override per handler
  • ✅ Typed PipelineContext<T> for passing data through the pipeline — no dictionaries

🔧 Design Overview​

NamespacePurpose
AbstractionsCore interfaces: IDispatcher, IRequest, IRequestHandler, INotification, IPipelineBehavior, INotificationBehavior
HandlersAbstract base classes: RequestHandler<TRequest>, RequestHandler<TRequest, TResponse>
BehaviorsBuilt-in pipeline and notification behaviors: logging, validation, metrics, retry
BuildersFluent builders: DispatcherBuilder<TRequest[, TResponse]>, NotificationDispatcherBuilder<TNotification>
ExtensionsDI registration extensions: AddMediator(), AddDispatcher(), AddNotification()
ModelsDelegates: RequestHandlerDelegate, NotificationHandlerDelegate; context carrier PipelineContext<T>
GeneratedRegistration queues fed by the in-package source generator
ExceptionsHandlerNotFoundException — 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​