101 lines
5.4 KiB
C#
101 lines
5.4 KiB
C#
using Microsoft.Extensions.DependencyInjection;
|
|
using Prefab.Handler.Decorators;
|
|
|
|
namespace Prefab.Handler;
|
|
|
|
|
|
/// <summary>
|
|
/// Provides a mechanism to execute handler pipelines with support for applying a sequence of handler decorators. This
|
|
/// class enables the composition of cross-cutting concerns around handler execution.
|
|
/// </summary>
|
|
/// <remarks>HandlerInvoker is typically used to execute handlers with additional behaviors, such as logging,
|
|
/// validation, or exception handling, by composing the provided decorators. The order of decorators affects the
|
|
/// execution flow: the first-registered decorator is invoked first and wraps subsequent decorators and the handler
|
|
/// itself. This class is thread-safe for concurrent handler executions.</remarks>
|
|
/// <param name="decorators">The collection of handler decorators to apply to each handler invocation. Decorators are applied in the order they
|
|
/// are provided, with the first decorator wrapping the outermost layer of the pipeline. Cannot be null.</param>
|
|
public sealed class HandlerInvoker(IEnumerable<IHandlerDecorator> decorators, IServiceProvider serviceProvider)
|
|
{
|
|
private readonly IHandlerDecorator[] _decorators = decorators.Reverse().ToArray();
|
|
|
|
public Task<TResponse> Execute<TResponse>(CancellationToken cancellationToken)
|
|
{
|
|
var inner = serviceProvider.GetRequiredService<IHandler<TResponse>>();
|
|
|
|
// No decorators? Call the handler directly.
|
|
if (_decorators.Length == 0)
|
|
return inner.Execute(cancellationToken);
|
|
|
|
var noRequest = new NoRequestAdapter<TResponse>(inner);
|
|
|
|
var pipeline = BuildPipeline(noRequest);
|
|
|
|
return pipeline(NoRequest.Instance, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the specified handler for the given request within the provided context and cancellation token,
|
|
/// returning the handler's response asynchronously.
|
|
/// </summary>
|
|
/// <typeparam name="TRequest">The type of the request to be handled. Must not be null.</typeparam>
|
|
/// <typeparam name="TResponse">The type of the response returned by the handler.</typeparam>
|
|
/// <param name="request">The request object to be processed by the handler.</param>
|
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
|
/// <returns>A task that represents the asynchronous operation. The task result contains the response produced by the
|
|
/// handler.</returns>
|
|
public Task<TResponse> Execute<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken) where TRequest : notnull
|
|
{
|
|
var handler = serviceProvider.GetRequiredService<IHandler<TRequest, TResponse>>();
|
|
|
|
// No decorators? Call the handler directly.
|
|
if (_decorators.Length == 0)
|
|
return handler.Execute(request, cancellationToken);
|
|
|
|
var pipeline = BuildPipeline(handler);
|
|
return pipeline(request, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds a delegate representing the request handling pipeline, applying all registered decorators to the
|
|
/// specified handler.
|
|
/// </summary>
|
|
/// <remarks>Decorators are applied in the order they were registered, with the first-registered decorator
|
|
/// wrapping the outermost layer of the pipeline. The returned delegate can be invoked with a request, handler
|
|
/// context, and cancellation token to execute the full pipeline.</remarks>
|
|
/// <typeparam name="TRequest">The type of the request message to be handled.</typeparam>
|
|
/// <typeparam name="TResponse">The type of the response message returned by the handler.</typeparam>
|
|
/// <param name="handler">The core handler to which decorators will be applied. Cannot be null.</param>
|
|
/// <returns>A delegate that processes a request through all registered decorators and the specified handler.</returns>
|
|
private Func<TRequest, CancellationToken, Task<TResponse>> BuildPipeline<TRequest, TResponse>(IHandler<TRequest, TResponse> handler)
|
|
{
|
|
// Start from the final operation: the handler itself.
|
|
Func<TRequest, CancellationToken, Task<TResponse>> pipeline = handler.Execute;
|
|
|
|
// Wrap in reverse registration order so the first-registered decorator runs outermost.
|
|
foreach (var decorator in _decorators)
|
|
{
|
|
var next = pipeline;
|
|
|
|
pipeline = (request, cancellationToken) =>
|
|
decorator.Invoke(next, request, cancellationToken);
|
|
}
|
|
|
|
return pipeline;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides an adapter that allows an existing handler to be used where a handler for a request type of NoRequest
|
|
/// is required.
|
|
/// </summary>
|
|
/// <remarks>This class is useful for scenarios where a handler expects no input request data but must
|
|
/// conform to an interface requiring a request parameter. It delegates execution to the specified inner handler,
|
|
/// ignoring the NoRequest parameter.</remarks>
|
|
/// <typeparam name="TResponse">The type of the response returned by the handler.</typeparam>
|
|
/// <param name="innerHandler">The handler that processes requests and produces a response of type TResponse.</param>
|
|
private sealed class NoRequestAdapter<TResponse>(IHandler<TResponse> innerHandler) : IHandler<NoRequest, TResponse>
|
|
{
|
|
public Task<TResponse> Execute(NoRequest _, CancellationToken cancellationToken)
|
|
=> innerHandler.Execute(cancellationToken);
|
|
}
|
|
}
|