【问题标题】:Autofac Lifetime Scope DecoratorAutofac 终身范围装饰器
【发布时间】:2015-07-05 12:24:50
【问题描述】:

我正在使用 Autofac 实现一个命令处理程序模式,并且正在使用它的装饰器工具来处理横切关注点,例如日志记录、身份验证等。

我还有一些依赖项,我只想将其范围限定为请求/响应管道的生命周期。

我在下面有一个示例实现:

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);
    }
}

如您所见,我将管道包装在一个名为 pipeline 的范围内,这意味着每次我将 LifetimeScopeTester 解析为 pipeline 的范围时,我都会得到相同的实例。

我认为我可以替换

using(var scope = container.BeginLifetimeScope("pipline")) {
    var pingHandler = scope.Resolve<IHandle<PingRequest, PingResponse>>();
    pingHandler.Handle(new PingRequest());
}

与

var pingHandler = scope.Resolve<IHandle<PingRequest, PingResponse>>();
pingHandler.Handle(new PingRequest());

通过创建另一个做同样事情的装饰器。

我的第一直觉是:

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;
    }
}

但是decoratedHandler 在注入时就已经解决了,所以这不起作用。

所以我尝试了:

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;
    }
}

但是,当调用 _decoratedHandlerFactory() 尝试再次用 LifetimeScopeHandler 装饰器包装内部处理程序时,这会无限重复。

我正在努力实现的目标。

我在https://dotnetfiddle.net/hwujNI 创建了一个dotnetfiddle 来演示这个问题。

【问题讨论】:

    标签: c# dependency-injection autofac


    【解决方案1】:

    当LifetimeScopeHandler 类的Handle 方法调用decoratedHandlerFactory 委托时,它要求Autofac 解析IHandle&lt;TRequest, TResponse&gt;,即LifetimeScopeHandler。这就是为什么你有一个StackOverflowException。我们可以将您的案例简化为此代码示例:

    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();
        }
    }
    

    即使有一个 Foo 实例,你也会有一个 StackOverflowException

    为了解决这个问题,您必须指出 Autofac LifetimeScopeHandler 的 decoratedHandlerFactory 代表不应是 LifetimeScopeHandler 的代表。

    您可以使用WithParameter 来指示最后一个使用特定参数的装饰器:

    builder.RegisterGenericDecorator(
        typeof(LifetimeScopeHandler<,>),
        typeof(IHandle<,>),
        "FirstDecoratorHandler"
    )
    .WithParameter((pi, c) => pi.Name == "decoratedHandlerFactory",
                   (pi, c) => c.ResolveKeyed("FirstDecoratorHandler", pi.ParameterType))
    .As(typeof(IHandle<,>));
    

    使用此配置,输出将是

    LifetimeScopeHandler
    FirstDecoratorHandler - LifetimeScopeTester[52243212]
    SecondDecoratorHandler - LifetimeScopeTester[52243212]
    PingHandler

    顺便说一句,您希望LifetimeScopeHandler 成为一种特殊的装饰器,它将在特殊范围内创建内部IHandler&lt;,&gt;。

    您可以通过要求LifetimeScopeHandler 为您创建正确的范围并解析之前的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;
            }
        }
    }
    

    此实现将要求LifetimeScopeHandler 知道链上的第一个装饰器,我们可以通过在其构造函数上发送名称来绕过它。

    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;
            }
        }
    }
    

    你必须像这样注册它:

    builder.RegisterGenericDecorator(
             typeof(LifetimeScopeHandler<,>),
             typeof(IHandle<,>),
             "FirstDecoratorHandler"
           )
           .WithParameter("previousHandlerName", "FirstDecoratorHandler")
           .As(typeof(IHandle<,>));
    

    我们也可以通过不使用RegisterGenericDecorator 方法绕过一切。

    如果我们像这样注册LifetimeScopeHandler:

    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<,>));
    

    而LifetimeScopeHandler 现在可以看起来像所有装饰器:

    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;
        }
    }
    

    顺便说一句,如果您在一个范围内使用多个IHandler&lt;,&gt;,并且您需要一个pipline 范围,则此解决方案可能会出现问题。要解决此问题,您可以查看此 dotnetfiddle :https://dotnetfiddle.net/rQgy2X,但在我看来它过于复杂,您可能不需要它。

    【讨论】:

    • Cyril,解决了一个问题,但是当我这样做并从 Main 中删除 container.BeginLifetimeScope("pipline") 时,我收到以下错误 - Run-time exception (line 139): No scope with a Tag matching 'pipline' is visible from the scope in which the instance was requested. .. 请参阅 dotnetfiddle.net/u5aMn4
    • @kimsagro 在您的第二个代码示例中,您不是从 pipline 范围内解析 IHandle&lt;PingRequest, PingResponse&gt;,而是直接从容器中解析。如果您从正确的范围内解决它,它就可以工作 (dotnetfiddle.net/fR9mTc)。
    • 我很困惑,LifetimeScopeHandler 的全部意义在于将管道包装在 pipeline 范围内,因此我不必将来自 Main 的调用包装在范围内
    • 好的,我更了解您的要求 :-)。类似的东西:dotnetfiddle.net/cSgJZ9 但有一个开放的.InstancePerOwned("FirstDecoratorHandler", typeof(IHandle&lt;PingRequest, PingResponse&gt;)) ...
    • 感谢您的帮助。问题是我可能想在多个处理程序中使用LifetimeScopeTester,我不想为每个处理程序将它注册为InstancePerOwned("FirstDecoratorHandler", typeof(IHandle&lt;PingRequest, PingResponse&gt;。我很困惑为什么dotnetfiddle.net/u5aMn4 的小提琴不起作用?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-22
    • 1970-01-01
    • 2017-08-15
    • 1970-01-01
    • 1970-01-01
    • 2011-06-29
    • 2021-07-05
    相关资源
    最近更新 更多