Autofac类拦截在某些设置中不起作用

Jac*_*goń 7 c# castle castle-dynamicproxy autofac

我有一个使用Autofac的IoC设置并使用AoP拦截器.

通常,我使用这样注册的接口拦截器:

var builder = new ContainerBuilder();
builder.RegisterType<MyType>()
    .As<IMyType>()
    .UsingConstructor(new Type[0])
    .EnableInterfaceInterceptors()
    .InterceptedBy(typeof(MyInterceptor));
Run Code Online (Sandbox Code Playgroud)

它有效.但由于某些原因(在这个最小的例子中不明显),我需要注册一个类并将其注入为self(不是通过接口),所以我尝试:

var builder = new ContainerBuilder();
builder.RegisterType<MyType>()
    .As<IMyType>()
    .AsSelf()
    .UsingConstructor(new Type[0])
    .EnableClassInterceptors()
    .InterceptedBy(typeof(MyInterceptor));
Run Code Online (Sandbox Code Playgroud)

在此设置中,拦截器永远不会被触发.当我在debug中检查注入的依赖项时,它似乎确实是一个子类代理(应该如此),但它的_interceptors私有属性只包含一个实例Castle.DynamicProxy.StandardInterceptor,这显然不是我配置的.

事实上,如果我删除AsSelf()它仍然没有拦截,这导致我得出一个结论,要么我做错了,或者类拦截根本不起作用......?

UPDATE

MyType实际上是从EF继承的DbContext,我试图拦截SaveChanges(),这是虚拟的.只是为了测试我添加了public virtual void Foo()哪些也没有截获.

UPDATE

我之前忽略了这一点,并简化了跳过一个重要的事实:我UsingConstructor()在注册中指定.现在我凭经验发现UsingConstructor()似乎阻止EnableClassInterceptors()了工作.

我正在使用的完整注册:

    builder.RegisterType<FooClass>()
        .AsSelf()
        .InstancePerRequest()
        .EnableClassInterceptors()
        .UsingConstructor(new Type[0]) // commenting this out solves the issue
        .InterceptedBy(typeof(MyInterceptor));

public class FooClass
{
    public virtual void Bar()
    {
        Debugger.Break();
    }
    public FooClass() { }
    public FooClass(int i) { }
}
Run Code Online (Sandbox Code Playgroud)

拦截器可用于其他注射; 这是复杂的代码,但我把断点放在public void Intercept(IInvocation invocation)方法的开头.

注释掉构造函数的选择使它再次起作用.

我将把奖金奖励给任何可以给我一个解决方法的人,或至少一个很好的解释为什么这不起作用.

UPDATE

关于添加构造函数参数的答案,我调查了这个方面,确实:

Foo.GetType().GetConstructors() // Foo is injected, proxied instance of FooClass
Run Code Online (Sandbox Code Playgroud)

实际上返回3个构造函数:

Void .ctor(Castle.DynamicProxy.IInterceptor[])
Void .ctor(Castle.DynamicProxy.IInterceptor[], Int32)
Void .ctor()
Run Code Online (Sandbox Code Playgroud)

无论我是否添加都会发生这种情况UseConstructor().巧合的是,代码并不适合我; 如果我指定了另一个构造函数(不是无参数),它会.

无论如何,我尝试了以下注册:

    builder.RegisterType<MyType>()
        .As<IMyType>()
        .AsSelf()
        .InstancePerRequest()
        .UsingConstructor(new[] { typeof(IInterceptor[]) })
        .EnableClassInterceptors()
        .InterceptedBy(typeof(MyInterceptor));
Run Code Online (Sandbox Code Playgroud)

......而且,惊喜,惊喜!它失败

MyType类型上不存在匹配的构造函数

进一步挖掘,改变那些流利方法的顺序最终解决了它,完整的工作注册是:

    builder.RegisterType<MyType>()
        .As<IMyType>()
        .AsSelf()
        .InstancePerRequest()
        .EnableClassInterceptors()
        .UsingConstructor(new[] { typeof(IInterceptor[]) })
        .InterceptedBy(typeof(MyInterceptor));
Run Code Online (Sandbox Code Playgroud)

摘要

我认为这是一个糟糕的API,它具有似乎流畅的API,但在很大程度上依赖于调用流畅方法的顺序.更糟糕的是,它实际上并没有抛出,只是无声地失败.

我还会认为从用户内部了解代理逻辑来设计糟糕的设计(单独留下文档).理想情况下,UsingConstructor()应该在匹配逻辑中封装拦截器的附加事实.也就是说,我自己设计了API,我知道它很容易要求但很难提供某些功能.

无论如何,案件已经结案,我想拥抱你们所有人.我相信是Jim Bolla给出了第一个精确答案,导致了突破,所以饼干给了他.如我错了请纠正我.

Jim*_*lla 6

当您使用EnableClassInterceptors(),Autofac时,告诉Castle Dynamic Proxy子类化您的类型.这个新的子类型获取新的构造函数签名,添加类型的参数IInterceptor[].同时,默认情况下,Autofac使用它MostParametersConstructorSelector来选择激活类型时要使用的构造函数.所以通常它会在具有IInterceptor[]参数的构造函数上匹配.

当您调用时UsingConstructor(),注册将更改为使用MatchingSignatureConstructorSelector与您指定的参数类型匹配.(在您的情况下,没有.)这会导致Autofac不使用接受IInterceptor[]参数的构造函数,从而导致您的拦截器不会传递到代理中.

我实际上很惊讶它不会因为没有匹配的构造函数而抛出异常,因为这就是代码看起来应该发生的事情.那部分我还没有解决.


sam*_*amy 2

编辑:Jim Bolla 的答案是正确的 - usingEnableClassInterceptors创建了一个子类型MyType,它将更改现有的构造函数以添加IInterceptor[]参数(以及无参数构造函数)。然后,您声明的拦截器InterceptedBy将传递给子类型。

添加UsingConstructor方法时,Autofac 将使用无参数构造函数创建子类型,该构造函数不会注册声明的拦截器。这就是为什么你没有达到预期的行为。可以通过强制 Autofac 使用新签名解析构造函数来使其工作。像这样:

var otherBuilder = new ContainerBuilder();
otherBuilder.RegisterType<MyType>()
    .As<IMyType>()
    .AsSelf()
    .EnableClassInterceptors()
    .InterceptedBy(typeof(MyInterceptor))
    .UsingConstructor(new Type[1] { typeof(IInterceptor[]) })
    ;
Run Code Online (Sandbox Code Playgroud)

这将按预期工作。这种行为坦率地让我感到惊讶,我本以为拦截器会在 Autofac/DynamicProxy 的第二步构建后添加。我想知道这是否可以被视为一个错误或至少是一个令人惊讶的行为。上面的解决方案有点味道,因为您必须更改构造函数签名才能容纳代理这一点并不明显。

如果您需要使用具有参数的构造函数,则IInterceptor[]参数始终首先添加到继承的构造函数中,因此例如使用int基于 - 的构造函数,您可以编写:.UsingConstructor(new Type[2] {typeof(IInterceptor[]), typeof(int) })


我认为您声明为虚拟和/或想要拦截的方法可能存在问题:这里有一些测试代码显示针对接口和类调用拦截器:

public interface IMyType { void method(); }
public class MyType: IMyType {
    public virtual void method() { Console.WriteLine("method"); }
    public virtual void method2() { Console.WriteLine("method2"); }
}

public class MyInterceptor : MethodInterceptor
{
    protected override void PreProceed(IInvocation invocation)
    {
        Console.Write("before - ");
    }
}

class Program
{
    static void Main(string[] args)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<MyType>()
            .As<IMyType>()
            .EnableInterfaceInterceptors()
            .InterceptedBy(typeof(MyInterceptor));
        builder.RegisterType<MyInterceptor>()
            .AsSelf();
        var container = builder.Build();

        var otherBuilder = new ContainerBuilder();
        otherBuilder.RegisterType<MyType>()
            .AsSelf()
            .As<IMyType>()
            .EnableClassInterceptors()
            .InterceptedBy(typeof(MyInterceptor));
        otherBuilder.RegisterType<MyInterceptor>()
            .AsSelf();
        var otherContainer = otherBuilder.Build();

        container.Resolve<IMyType>().method();
        // outputs -> before - method
        otherContainer.Resolve<IMyType>().method();
        // outputs -> before - method
        otherContainer.Resolve<MyType>().method();
        // outputs -> before - method
        otherContainer.Resolve<MyType>().method2();
        // outputs -> before - method2
    }
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,第二次注册在所有情况下都会调用拦截器:

  • 通过接口解析并调用接口方法
  • 通过类解析并调用接口方法
  • 通过类来解析并调用类方法

您确定注册正确吗?对于第二种情况(IMyTypeMyType),您有何解决办法?