MediatR - Can not create new Instance of TResponse - c#

I am trying to create a new PipelineBehavior that returns a new Instance of TResponse.
With this code if I put a break point to the "handle" I can see that the pipeline works.
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : Response
{
private readonly ILogger<ValidationBehaviour<TRequest, TResponse>> _logger;
private readonly IValidationHandler<TRequest> _validationHandler;
public ValidationBehaviour(ILogger<ValidationBehaviour<TRequest, TResponse>> logger)
{
this._logger = logger;
}
public ValidationBehaviour(ILogger<ValidationBehaviour<TRequest, TResponse>> logger, IValidationHandler<TRequest> validationHandler)
{
this._logger = logger;
this._validationHandler = validationHandler;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var requestName = request.GetType();
_logger.LogInformation("{Request} does not have a validation handler configured.", requestName);
return await next();
}
}
but I can't have this syntax
return new TResponse { StatusCode = (int)HttpStatusCode.BadRequest, SFADevErrorMessage = result.Error };
I have seen examples where they use the "new()" keyword in the constraint and that allows them to create a new Instance of TResponse. Like that.
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : Response, new()
If I do the same I am able to do that new TResponse { StatusCode = (int)HttpStatusCode.BadRequest, SFADevErrorMessage = result.Error };
but then my pipeline is not working - is not executing and the executable code never hits my break point.
Any idea why this is happening? Am I missing something?

The actual issue was my where T : new(), had nothing to do with mediatr.
I found the answer here
The TResponse was type of Response (a class I created, for all the responses of my API) and that needed a default public constructor

Related

MediatR PipelineBehavior error message Unable to resolve service for type 'FluentValidation.IValidator`1

I have this ValidationBehavior
public sealed class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IValidator<TRequest> _validator;
public ValidationBehavior(IValidator<TRequest> validator)
{
_validator = validator;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
_validator.ValidateAndThrow(request);
return await next();
}
}
I have this handler
public class RemoveCurrencyHandler : IRequestHandler<RemoveCurrencyCommand, Unit>
{
private readonly ApplicationContext _context;
public RemoveCurrencyHandler(ApplicationContext context)
{
_context = context;
}
public async Task<Unit> Handle(RemoveCurrencyCommand request, CancellationToken cancellationToken)
{
var currency = await _context.Currency.FindAsync(request.Id);
if (currency is null)
throw new KeyNotFoundException();
_context.Remove(currency);
await _context.SaveChangesAsync();
return Unit.Value;
}
}
I'm getting error message Unable to resolve service for type 'FluentValidation.IValidator' everytime I call this handler, now obviously I know the reason is because I'm missing the validator, so it goes away if I add this
public class RemoveCurrencyValidator : AbstractValidator<RemoveCurrencyCommand>
{
}
but not all my handler need a Validator, so I don't want to add empty Validator class to handler that doesn't need it. Is there any alternative?
The MediatR validator pieplines enable you to execute validation logic before and after your Command or Query Handlers execute.
You can update the constructor of ValidationBehavior class to expect an IEnumerable<IValidator>. So that we can always resolve the class (the list will be empty if you don't implement the Validator)
// ValidationBehavior
public sealed class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumrable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if(_validators.Any())
{
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAndThrow(request)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
}
return await next();
}
}

Is there a way to skip MediatR Pipeline?

I would like to cache some responses from CommandsHandlers.
I Already did this using IPipelineBehaviour, but only 5% of my requests really must have cache, and the other 95% must skip this Pipeline. Is there a way to do that?
Below follows my code.
Thanks!
public class PipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>, IProvideCacheKey
{
private readonly IMemoryCache _cache;
public PipelineBehavior(IMemoryCache cache)
{
_cache = cache;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken,
RequestHandlerDelegate<TResponse> next)
{
// Check in cache if we already have what we're looking for
var cacheKey = request.CacheKey;
if (_cache.TryGetValue<TResponse>(cacheKey, out var cachedResponse))
{
return cachedResponse;
}
// If we don't, execute the rest of the pipeline, and add the result to the cache
var response = await next();
_cache.Set(cacheKey, response);
return response;
}
}
public class GetUserByEmailCommand : Command, IRequest<bool>, IProvideCacheKey
{
public string Email { get; set; }
public string CacheKey => $"{GetType().Name}:{Email}";
public override bool IsValid()
{
ValidationResult = new GetUserByEmailCommandValidation<GetUserByEmailCommand>().Validate(this);
return ValidationResult.IsValid;
}
}
public interface IProvideCacheKey
{
string CacheKey { get; }
}
You can wrap your caching behavior in a check that is bypassed if the request is not cacheable to let the pipeline continue. In your case, you can probably just check if the request implements your interface at the start of the Handle method:
if (request is IProvideCacheKey)
{
// perform cache behavior, return if cached and terminate the pipeline
}
// else continue the pipeline
There's a couple good examples of this in more detail at:
https://lurumad.github.io/cross-cutting-concerns-in-asp-net-core-with-meaditr
https://anderly.com/2019/12/12/cross-cutting-concerns-with-mediatr-pipeline-behaviors/
Instead of skipping behaviors, it may be better to construct multiple pipelines with different behaviors.
Register pipeline behaviors:
// pipeline 1's behaviors
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(FooBehavior<,>));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(BarBehavior<,>));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(BazBehavior<,>));
// pipeline 2's behaviors
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(CatBehavior<,>));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(DogBehavior<,>));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(PigBehavior<,>));
Define pipelines:
// pipeline 1
public interface IPipeline1 { }
public class FooBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IPipeline1 { }
public class BarBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IPipeline1 { }
public class BazBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IPipeline1 { }
// pipeline 2
public interface IPipeline2 { }
public class CatBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IPipeline2 { }
public class DogBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IPipeline2 { }
public class PigBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IPipeline2 { }
Define requests:
// requests to be processed by pipeline 1
public class ARequest : IRequest, IPipeline1 { }
public class BRequest : IRequest, IPipeline1 { }
public class CRequest : IRequest, IPipeline1 { }
// requests to be processed by pipeline 2
public class XRequest : IRequest, IPipeline2 { }
public class YRequest : IRequest, IPipeline2 { }
public class ZRequest : IRequest, IPipeline2 { }
I faced the same problem.
I fixed it by checking in the Pipeline Behaviour Handler if the request implements the interface.
If the request implements the interface ICachable, that means that the request will go through the cachable logic.
With this change, there is no need to use the where TRequest : IQueryable .
If you leave this constrain it will throw an error when trying to
process a Query or Command that doesn't implement the IQueryable
interface.
If you are wondering how to access the interface properties the answer is by creating a variable that implements the interface.
theObject is theInterface newObjectWithInterfaceProperties
.
{
public class CacheBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
//where TRequest : ICacheableQuery
{
private readonly IMemoryCache _memoryCache;
private readonly ILogger<CacheBehaviour<TRequest, TResponse>> _logger;
public CacheBehaviour(IMemoryCache memoryCache, ILogger<CacheBehaviour<TRequest, TResponse>> logger)
{
_memoryCache = memoryCache;
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken,
RequestHandlerDelegate<TResponse> next)
{
//Check if the request implements ICachableQuery
//If it does then it creates a cachableRequest variable that will contain the properties of the ICachableQuery interface.
if (request is ICachableQuery cachableRequest)
{
var requestName = request.GetType().Name;
_logger.LogInformation($"Request : {requestName} is configured to cache");
TResponse response;
if(_memoryCache.TryGetValue(cachableRequest.CacheKey, out response))
{
_logger.LogInformation($"Request: {requestName} returning response from cache");
return response;
}
response = await next();
_logger.LogInformation($"Request: {requestName} returning response from DB");
_memoryCache.Set(cachableRequest.CacheKey, response);
return response;
}
return await next();
}
}
}

How to register mediator's IPipelineBehavior with Autofac

I'm trying to understand how to set up pipeline behaviours with mediator.
My command:
public class MyUpdateCommand : IRequest<CommandResult>
{
// fields
}
My handler:
public class MyUpdateCommandHandler : RequestHandler<MyUpdateCommand, CommandResult>
{
private readonly IMyRepository _repository;
public GasDetailsUpdateCommandHandler(IMyRepository repository)
{
_repository = repository;
}
protected override CommandResult HandleCore(MyUpdateCommand command)
{
{
_repository.Update(command);
return CommandResult.Success;
}
}
}
Now, let's say I want to handle all exceptions raised in RequestHandlers that return a CommandResult by logging them and returning the message in my CommandResult object. I've created this class:
public class ExceptionLoggingBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse> where TResponse : CommandResult
{
private readonly ILog _logger;
public ExceptionLoggingBehavior(ILog logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request,
CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
try
{
return await next();
}
catch (Exception ex)
{
_logger.Error(ex.Message);
return (TResponse) CommandResult.Fail(ex.Message);
}
}
}
What do I need to put into my inversion of control container (AutoFac)?

Return response with errors instead of throwing exception in validation pipeline mediatr 3

I am currently working with Pipeline behavior in Mediatr 3 for request validation. All the examples that I came across were throwing ValidationException if any failures happening, instead of doing that I want to return the response with the error. Anyone has idea on how to do it?
Below is the code for the validation pipeline:
public class ValidationPipeline<TRequest, TResponse> :
IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationPipeline(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
var failures = _validators
.Select(v => v.Validate(request))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
{
throw new ValidationException(failures);
}
return next();
}
}
Note: I found this question Handling errors/exceptions in a mediator pipeline using CQRS? and I am interested in the 1st option on the answer, but no clear example on how to do that.
This is my response class:
public class ResponseBase : ValidationResult
{
public ResponseBase() : base() { }
public ResponseBase(IEnumerable<ValidationFailure> failures) : base(failures) {
}
}
and I added below signature in the validation pipeline class:
public class ValidationPipeline<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
where TResponse : ResponseBase
I did this then in the Handle method:
var response = new ResponseBase(failures);
return Task.FromResult<TResponse>(response);
But that gave me error 'cannot convert to TResponse'.
Several years ago, I created general Result object, which I am constantly improving. It is quite simple, check https://github.com/martinbrabec/mbtools.
If you will be ok with the Result (or Result<>) being the return type every method in Application layer, then you can use the ValidationBehavior like this:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : Result, new()
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext(request);
List<ValidationFailure> failures = _validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
{
TResponse response = new TResponse();
response.Set(ErrorType.NotValid, failures.Select(s => s.ErrorMessage), null);
return Task.FromResult<TResponse>(response);
}
else
{
return next();
}
}
return next();
}
}
Since all your handlers return Result (or Result<>, which is based upon Result), you will be able to handle all validation errors without any exception.
Simply don't call next if there's any failures:
public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
var failures = _validators
.Select(v => v.Validate(request))
.SelectMany(result => result.Errors)
.Where(f => f != null)
.ToList();
if (failures.Any())
{
var response = new Thing(); //obviously a type conforming to TResponse
response.Failures = failures; //I'm making an assumption on the property name here.
return Task.FromResult(response);
}
else
{
return next();
}
}
Note:
Your class (Thing in my example) must be of type TResponse
You can configure validation handling using package
https://www.nuget.org/packages/MediatR.Extensions.FluentValidation.AspNetCore
Just insert in configuration section:
services.AddFluentValidation(new[] {typeof(GenerateInvoiceHandler).GetTypeInfo().Assembly});
GitHub

Autofac Lifetime Scope Decorator

I am implementing a command handler pattern using Autofac and am using it's decorator facility handle cross cutting concerns such as logging, authentication etc.
I also have dependencies that I only want scoped to the lifetime of the request / response pipeline.
I have an example implementation below:
public class Program
{
public static void Main()
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyModules(typeof(HandlerModule).Assembly);
builder.RegisterType<LifetimeScopeTester>().AsSelf()
.InstancePerMatchingLifetimeScope("pipline");
var container = builder.Build();
using(var scope = container.BeginLifetimeScope("pipline")) {
var pingHandler = scope.Resolve<IHandle<PingRequest, PingResponse>>();
pingHandler.Handle(new PingRequest());
}
}
}
public class HandlerModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(ThisAssembly)
.As(type => type.GetInterfaces()
.Where(interfaceType => interfaceType.IsClosedTypeOf(typeof (IHandle<,>)))
.Select(interfaceType => new KeyedService("IHandle", interfaceType)));
builder.RegisterGenericDecorator(
typeof(SecondDecoratorHandler<,>),
typeof(IHandle<,>),
"IHandle"
)
.Keyed("SecondDecoratorHandler", typeof(IHandle<,>));
builder.RegisterGenericDecorator(
typeof(FirstDecoratorHandler<,>),
typeof(IHandle<,>),
"SecondDecoratorHandler"
);
}
}
public class LifetimeScopeTester {}
public interface IHandle<in TRequest, out TResponse>
where TRequest : class, IRequest<TResponse>
{
TResponse Handle(TRequest request);
}
public interface IRequest<TResponse> {
}
public class PingRequest : IRequest<PingResponse> {
}
public class PingResponse {
}
public class PingHandler : IHandle<PingRequest, PingResponse> {
public PingResponse Handle(PingRequest request) {
Console.WriteLine("PingHandler");
return new PingResponse();
}
}
public class FirstDecoratorHandler<TRequest, TResponse> : IHandle<TRequest, TResponse>
where TRequest : class, IRequest<TResponse>
{
private readonly IHandle<TRequest, TResponse> _decoratedHandler;
private readonly LifetimeScopeTester _lifetimeScopeTester;
public FirstDecoratorHandler(IHandle<TRequest, TResponse> decoratedHandler,
LifetimeScopeTester lifetimeScopeTester)
{
_decoratedHandler = decoratedHandler;
_lifetimeScopeTester = lifetimeScopeTester;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("FirstDecoratorHandler - LifetimeScopeTester[{0}]",
_lifetimeScopeTester.GetHashCode());
return _decoratedHandler.Handle(request);
}
}
public class SecondDecoratorHandler<TRequest, TResponse> : IHandle<TRequest, TResponse>
where TRequest : class, IRequest<TResponse>
{
private readonly IHandle<TRequest, TResponse> _decoratedHandler;
private readonly LifetimeScopeTester _lifetimeScopeTester;
public SecondDecoratorHandler(IHandle<TRequest, TResponse> decoratedHandler, LifetimeScopeTester lifetimeScopeTester)
{
_decoratedHandler = decoratedHandler;
_lifetimeScopeTester = lifetimeScopeTester;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("SecondDecoratorHandler - LifetimeScopeTester[{0}]", _lifetimeScopeTester.GetHashCode());
return _decoratedHandler.Handle(request);
}
}
As you can see, I wrap the pipleine in a scope named pipeline which means that everytime I resolve LifetimeScopeTester which is scope to pipeline, I get the same instance.
I as thinking that I might be able to replace
using(var scope = container.BeginLifetimeScope("pipline")) {
var pingHandler = scope.Resolve<IHandle<PingRequest, PingResponse>>();
pingHandler.Handle(new PingRequest());
}
with
var pingHandler = scope.Resolve<IHandle<PingRequest, PingResponse>>();
pingHandler.Handle(new PingRequest());
by creating another decorator that does that same thing.
My first instinct was:
public class LifetimeScopeDecoratorHandler<TRequest, TResponse> : IHandle<TRequest, TResponse>
where TRequest : class, IRequest<TResponse>
{
private readonly ILifetimeScope _scope;
private readonly IHandle<TRequest, TResponse> _decoratedHandler;
public LifetimeScopeDecoratorHandlerAttempt1(ILifetimeScope scope,
IHandle<TRequest, TResponse> decoratedHandler)
{
_scope = scope;
_decoratedHandler = decoratedHandler;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("LifetimeScopeDecoratorHandler");
TResponse response;
using (_scope.BeginLifetimeScope("pipeline"))
{
response = _decoratedHandler.Handle(request);
}
return response;
}
}
But the decoratedHandler would have already been resolved by the time it's injected so that won't work.
So I tried:
public class LifetimeScopeHandler<TRequest, TResponse> : IHandle<TRequest, TResponse>
where TRequest : class, IRequest<TResponse>
{
private readonly ILifetimeScope _scope;
private readonly Func<IHandle<TRequest, TResponse>> _decoratedHandlerFactory;
public LifetimeScopeHandler(ILifetimeScope scope,
Func<IHandle<TRequest, TResponse>> decoratedHandlerFactory)
{
_scope = scope;
_decoratedHandlerFactory = decoratedHandlerFactory;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("LifetimeScopeDecoratorHandler");
TResponse response;
using (_scope.BeginLifetimeScope("pipeline"))
{
var decoratedHandler = _decoratedHandlerFactory();
response = decoratedHandler.Handle(request);
}
return response;
}
}
However this repeated infinitely as calling _decoratedHandlerFactory() tries to wrap the inner handler with a LifetimeScopeHandler decorator again.
Is what I'm trying to achieve possible.
I have created a dotnetfiddle at https://dotnetfiddle.net/hwujNI demonstrating the issue.
When the Handle method of LifetimeScopeHandler class invoke the decoratedHandlerFactory delegate, it asks Autofac to resolve a IHandle<TRequest, TResponse> which is a LifetimeScopeHandler. That's why you have a StackOverflowException. We can simplify your case to this code sample :
public class Foo
{
public Foo(Func<Foo> fooFactory)
{
this._fooFactory = fooFactory;
}
private readonly Func<Foo> _fooFactory;
public void Do()
{
Foo f = this._fooFactory();
f.Do();
}
}
Even if there is a single instance of Foo you will have a StackOverflowException
In order to resolve this issue, you have to indicate Autofac that the decoratedHandlerFactory delegate of LifetimeScopeHandler should not be a delegate of LifetimeScopeHandler.
You can use the WithParameter to indicate the last decorator to use a specific parameter :
builder.RegisterGenericDecorator(
typeof(LifetimeScopeHandler<,>),
typeof(IHandle<,>),
"FirstDecoratorHandler"
)
.WithParameter((pi, c) => pi.Name == "decoratedHandlerFactory",
(pi, c) => c.ResolveKeyed("FirstDecoratorHandler", pi.ParameterType))
.As(typeof(IHandle<,>));
With this configuration, the output will be
LifetimeScopeHandler
FirstDecoratorHandler - LifetimeScopeTester[52243212]
SecondDecoratorHandler - LifetimeScopeTester[52243212]
PingHandler
By the way, you want LifetimeScopeHandler to be a special kind of decorator that will create the inner IHandler<,> in a special scope.
You can do this by asking the LifetimeScopeHandler to create the correct scope for you and resolve the previous Ihandler.
public class LifetimeScopeHandler<TRequest, TResponse>
: IHandle<TRequest, TResponse> where TRequest : class, IRequest<TResponse>
{
private readonly ILifetimeScope _scope;
public LifetimeScopeHandler(ILifetimeScope scope)
{
this._scope = scope;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("LifetimeScopeDecoratorHandler");
using (ILifetimeScope s = this._scope.BeginLifetimeScope("pipline"))
{
var decoratedHandler =
s.ResolveKeyed<IHandle<TRequest, TResponse>>("FirstDecoratorHandler");
TResponse response = decoratedHandler.Handle(request);
return response;
}
}
}
This implementation will require that LifetimeScopeHandler knows the first decorator on the chain, we can bypass that by sending the name on its constructor.
public class LifetimeScopeHandler<TRequest, TResponse>
: IHandle<TRequest, TResponse> where TRequest : class, IRequest<TResponse>
{
private readonly ILifetimeScope _scope;
private readonly String _previousHandlerName;
public LifetimeScopeHandler(ILifetimeScope scope, String previousHandlerName)
{
this._scope = scope;
this._previousHandlerName = previousHandlerName;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("LifetimeScopeDecoratorHandler");
using (ILifetimeScope s = this._scope.BeginLifetimeScope("pipline"))
{
var decoratedHandler =
s.ResolveKeyed<IHandle<TRequest, TResponse>>(previousHandlerName);
TResponse response = decoratedHandler.Handle(request);
return response;
}
}
}
And you will have to register it like this :
builder.RegisterGenericDecorator(
typeof(LifetimeScopeHandler<,>),
typeof(IHandle<,>),
"FirstDecoratorHandler"
)
.WithParameter("previousHandlerName", "FirstDecoratorHandler")
.As(typeof(IHandle<,>));
We can also bypass everything by not using the RegisterGenericDecorator method.
If we register the LifetimeScopeHandler like this :
builder.RegisterGeneric(typeof(LifetimeScopeHandler<,>))
.WithParameter((pi, c) => pi.Name == "decoratedHandler",
(pi, c) =>
{
ILifetimeScope scope = c.Resolve<ILifetimeScope>();
ILifetimeScope piplineScope = scope.BeginLifetimeScope("pipline");
var o = piplineScope.ResolveKeyed("FirstDecoratorHandler", pi.ParameterType);
scope.Disposer.AddInstanceForDisposal(piplineScope);
return o;
})
.As(typeof(IHandle<,>));
And LifetimeScopeHandler can now look like all decorator :
public class LifetimeScopeHandler<TRequest, TResponse>
: IHandle<TRequest, TResponse> where TRequest : class, IRequest<TResponse>
{
private readonly IHandle<TRequest, TResponse> _decoratedHandler;
public LifetimeScopeHandler(IHandle<TRequest, TResponse> decoratedHandler)
{
this._decoratedHandler = decoratedHandler;
}
public TResponse Handle(TRequest request)
{
Console.WriteLine("LifetimeScopeDecoratorHandler");
TResponse response = this._decoratedHandler.Handle(request);
return response;
}
}
By the way, this solution may have an issue if you use more than one IHandler<,> in a scope and you need to have a single pipline scope. To resolve this, you can see this dotnetfiddle : https://dotnetfiddle.net/rQgy2X but it seems to me over complicated and you may not need it.

Categories

Resources