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.