RequestHandlerDelegate
Namespace: RA.Utilities.Feature.Models
The RequestHandlerDelegate delegate types are fundamental to how the RA.Utilities.Feature library processes requests.
They represent the "rest of the pipeline" that an IPipelineBehavior invokes via next().
There are four RequestHandlerDelegate types:
1. public delegate Task RequestHandlerDelegate();
- Purpose: This delegate represents an asynchronous callback for handling requests that do not return a value.
- Usage:
It's used in the
Mediator.Send<TRequest>path (and byIPipelineBehavior<TRequest>), whereTRequestimplementsIRequest.
2. public delegate Task<TResponse> RequestHandlerDelegate<TResponse>();
- Purpose:
This delegate represents an asynchronous callback for handling requests that are expected to return a value of type
TResponse. - Usage: It's used in the
Mediator.Send<TRequest, TResponse>path (and byIPipelineBehavior<TRequest, TResponse>), whereTRequestimplementsIRequest<TResponse>.
3. public delegate Task RequestHandlerContextDelegate<TContext>(PipelineContext<TContext> context) where TContext : class, new();
- Purpose:
A context-aware callback for requests without a response. It receives the
PipelineContext<TContext>flowing through the pipeline. - Usage: Used by the context-aware overload of
IPipelineBehavior<TRequest>.
4. public delegate Task<TResponse> RequestHandlerContextDelegate<TResponse, TContext>(PipelineContext<TContext> context) where TContext : class, new();
- Purpose: A context-aware callback for requests with a response.
- Usage: Used by the context-aware overload of
IPipelineBehavior<TRequest, TResponse>.
✨ Key Purposes and How They Work:
-
Enabling Pipeline Behaviors (
IPipelineBehavior): Just likeNotificationHandlerDelegatefor notifications,RequestHandlerDelegate(and its generic counterpart) is the cornerstone for implementingIPipelineBehaviors. TheHandleAsyncmethod of anIPipelineBehaviorsreceives one of these delegates as itsnextparameter. Thisnextdelegate represents "the rest of the pipeline" – either the subsequent behavior or the actualIRequestHandler. -
Facilitating Cross-Cutting Concerns: By passing the next delegate,
IPipelineBehaviorsimplementations can wrap the execution of the core request handling logic. This allows for the clean implementation of cross-cutting concerns such as:
- Validation: Validate the request before calling
await next(). If validation fails, the behavior can short-circuit the pipeline by throwing an exception (for example,ValidationBehaviorthrows aBadRequestException) without ever invoking the actual handler. - Logging: Log request details before and after the handler executes.
- Error Handling: Wrap the
await next()call in a try-catch block to translate or observe exceptions. - Transactions: Begin a database transaction before
await next()and commit/rollback based on the outcome.
- Plain-task contract:
The delegates return
TaskorTask<TResponse>directly — they do not carry aResultwrapper. Success is the returned value (or completion of theTask); failures propagate as exceptions out of the pipeline to the API layer'sGlobalExceptionHandler.
🚀 Example Usage
Let's look at how these delegates are constructed and used in the Mediator's Send methods:
For requests with a response (Send<TRequest, TResponse>):
// From: /RA.Utilities/Application/RA.Utilities.Feature/Mediator.cs
public Task<TResponse> Send<TRequest, TResponse>(
TRequest request,
CancellationToken cancellationToken = default)
where TRequest : IRequest<TResponse>
{
// ... (handler and behaviors retrieval) ...
// 1. The core handler's execution is encapsulated in a RequestHandlerDelegate<TResponse>.
Task<TResponse> HandlerDelegate() => handler.HandleAsync(request, cancellationToken);
// 2. The pipeline is built by aggregating behaviors around the core delegate.
// The 'nextDelegate' parameter in the Aggregate function is the delegate from the previous iteration (or the core handler initially).
// The lambda creates a new delegate that calls the current behavior, passing the 'nextDelegate' to it.
RequestHandlerDelegate<TResponse> next = behaviors
.Reverse() // Behaviors are applied in reverse order of registration to ensure the first registered is outermost.
.Aggregate((RequestHandlerDelegate<TResponse>)HandlerDelegate,
(nextDelegate, behavior) => () => behavior.HandleAsync(request, nextDelegate, cancellationToken));
// 3. The outermost delegate (representing the entire pipeline) is executed.
return next();
}
For requests without a response (Send<TRequest>):
// From: /RA.Utilities/Application/RA.Utilities.Feature/Mediator.cs
public Task Send<TRequest>(
TRequest request,
CancellationToken cancellationToken = default)
where TRequest : IRequest
{
// ... (handler and behaviors retrieval) ...
// 1. The core handler's execution is encapsulated in a RequestHandlerDelegate.
Task HandlerDelegate() => handler.HandleAsync(request, cancellationToken);
// 2. The pipeline is built similarly, but using the non-generic RequestHandlerDelegate.
RequestHandlerDelegate next = behaviors
.Reverse()
.Aggregate((RequestHandlerDelegate)HandlerDelegate,
(nextDelegate, behavior) => () => behavior.HandleAsync(request, nextDelegate, cancellationToken));
// 3. The outermost delegate is executed.
return next();
}