This mediator enables the definition of different pipelines for various request types, allowing for greater flexibility and control.
public interface IRequestA { }
To implement the Mediator
class without relying on 'C# Reflection', define the request type as a generic argument within the IRequest
interface.
public interface IRequest<in TRequest, out TResponse> { }
public sealed class RequestX : IRequest<RequestX, string>, IRequestA
{
// Implementation details
}
public sealed class RequestXHandler : IRequestHandler<RequestX, string>
{
public Task<string> Handle(RequestX request, CancellationToken cancellationToken)
{
// ...
}
}
Custom middleware can be created to process general or specific request types. C#'s generic type constraints (where
keyword) can be used to enforce type restrictions.
public sealed class ExceptionHandlingMiddleware<TRequest, TResponse> : IMiddleware<TRequest, TResponse>
{
public async Task<TResponse> Handle(RequestContext<TRequest> context, IRequestProcessor<TRequest, TResponse> next)
{
try
{
return await next.Handle(context);
}
catch (Exception exp)
{
// Exception handling logic
}
}
}
public sealed class SpecialMiddleware<TRequest, TResponse> : IMiddleware<TRequest, TResponse>
where TRequest : ISpecialRequest
{
// Middleware implementation
}
Pipelines can be defined in two ways:
The Pipeline
base class accepts a request handler (IRequestHandler
) along with pipeline middlewares (IMiddleware
) as input parameters.
public abstract class Pipeline<TRequest, TResponse> : IPipeline<TRequest, TResponse>
where TRequest : IRequest<TRequest, TResponse>
{
protected Pipeline(
IRequestHandler<TRequest, TResponse> handler,
params IMiddleware<TRequest, TResponse>[] middlewares)
{
// Base pipeline implementation
}
}
public abstract class PipelineA<TRequest, TResponse> : Pipeline<TRequest, TResponse>
where TRequest : IRequest<TRequest, TResponse>, IRequestA
{
public PipelineA(
IRequestHandler<TRequest, TResponse> handler,
ExceptionHandlingMiddleware<TRequest, TResponse> exceptionHandling,
ValidationMiddleware<TRequest, TResponse> validation)
: base(handler, exceptionHandling, validation)
{ }
}
Register the pipeline in Dependency Injection (DI):
services.AddScoped(typeof(IPipeline<,>), typeof(PipelineA<,>));
This approach involves defining a uniquely named pipeline class along with a configuration class that implements the IKeyedPipelineConfiguration
interface.
public abstract class KeyedPipeline<TRequest, TResponse> : IPipeline<TRequest, TResponse>
where TRequest : IRequest<TRequest, TResponse>
{
protected KeyedPipeline(IServiceProvider serviceProvider, string pipelineName)
{
// ...
}
// Keyed pipeline base class implementation
}
internal sealed class PipelineB<TRequest, TResponse> : KeyedPipeline<TRequest, TResponse>
where TRequest : IRequest<TRequest, TResponse>, IRequestB
{
public PipelineB(IServiceProvider serviceProvider)
: base(serviceProvider, PipelineBConfiguration.PipelineName)
{ }
}
internal sealed class PipelineBConfiguration : IKeyedPipelineConfiguration
{
public static string PipelineName { get; } = "Pipeline_B";
public static Type[] Middlewares()
{
return
[
typeof(ExceptionHandlingMiddleware<,>),
typeof(ValidationMiddleware<,>),
typeof(SpecialMiddleware<,>),
];
}
}
Register the pipeline in DI:
services.AddTransient(typeof(IPipeline<,>), typeof(PipelineB<,>));
services.RegisterMiddlewares<PipelineBConfiguration>();
private static async Task Sample(IMediator mediator, CancellationToken cancellationToken)
{
var request = new RequestX() { /* ... */ };
var response = await mediator.Send(request, cancellationToken);
// ...
}