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

时间:2014-08-22 14:53:54

标签: c# autofac castle castle-dynamicproxy

我使用Autofac进行IoC设置并使用AoP拦截器。

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

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

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

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

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

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

更新

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

更新

我之前忽略了这一点并简化了跳过一个重要事实:我在注册中指定了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) { }
}

拦截器适用于其他注射;它是复杂的代码,但我在public void Intercept(IInvocation invocation)方法的开头设置了断点。

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

我将奖励那些可以给我一个解决方法的人,或者至少可以解释为什么这样做不起作用。

更新

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

Foo.GetType().GetConstructors() // Foo is injected, proxied instance of FooClass

实际上返回 3 构造函数:

Void .ctor(Castle.DynamicProxy.IInterceptor[])
Void .ctor(Castle.DynamicProxy.IInterceptor[], Int32)
Void .ctor()

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

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

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

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

  

MyType

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

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

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

概要

我认为使用看起来流畅的API但却很大程度上取决于调用流畅方法的顺序是不好的API。更糟糕的是,它实际上并没有抛出,只是无声地失败

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

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

2 个答案:

答案 0 :(得分:6)

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

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

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

答案 1 :(得分:2)

编辑:Jim Bolla的回答是正确的 - 使用EnableClassInterceptors创建了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[]) })
    ;

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

如果需要使用具有参数的构造函数,则始终在继承的构造函数中添加IInterceptor[]参数,例如使用基于int的构造函数编写:{{1} }


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

.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 } } IMyType)你会解决什么问题?