Autofac class hook does not work in specific setting

I have install IoC with Autofac and use AoP interceptors.

I usually use front-end interceptors registered as follows:

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

And it works. But for certain reasons (not explicit in this minimal example) I need one class for registration and input as well as myself (not via the interface), so I try:

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

In this setting, the interceptor never starts. When I test the injected dependency in debugging, it seems to be a subclass proxy (as it should be), but its private _interceptors property contains only one instance of Castle.DynamicProxy.StandardInterceptor , which is obviously not what I configured.

In fact, if I delete AsSelf() , it still doesn’t intercept, which leads me to the conclusion that either I am doing something wrong, or intercepting a class simply does not work ...?

UPDATE

MyType actually inherits from EF DbContext , and I'm trying to intercept SaveChanges() , which is virtual. Just for the test, I added public virtual void Foo() , which was also not intercepted.

UPDATE

I forgot about this before and simplified the important fact I missed: I specify UsingConstructor() in the registry. Now, I have empirically discovered that UsingConstructor() seems to prevent EnableClassInterceptors() from working.

Full registration I am currently using:

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

Interceptor works for other injections; this is complex code, but I put a breakpoint at the beginning of the public void Intercept(IInvocation invocation) method.

Commenting on the choice of constructor, it works again.

I will award a reward to anyone who can give me a workaround, or at least a good explanation of why this does not work.

UPDATE

With your answers about the added constructor options, I explored this aspect and really:

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

really returns 3 constructors:

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

This happens if I UseConstructor() or not. Coincidentally, the code did not drop for me; this would be if I specified a different constructor (and not without parameters).

Regardless, I tried the following registration:

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

... and, surprise, surprise! he fails with

No match constructor exists in type MyType

We dig further, changing the order of these freely used methods solves it ultimately, with full registration of work:

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

ESSENCE

I would consider that a bad API has what appears to be a free API, but at the same time it depends heavily on the order in which the fast methods are used. Even worse, he doesn’t actually quit, he just fails.

I would also think of poor design (leave only documentation) in order to require internal knowledge of proxy logic from users. Ideally, UseConstructor () should encapsulate the added fact of interceptors in the matching logic. However, I developed the API myself, and I know that it is easy to require, but it is difficult to deliver certain functions.

In any case, the case is closed, and I would like to hug you all. I believe that it was Jim Ball who gave the first exact answer, which led to a breakthrough, so that cookies go to him. Correct me if I am wrong.

+7
c # autofac castle-dynamicproxy castle
source share
2 answers

EDIT : Jim Ball's answer is right - using EnableClassInterceptors creates a subtype of MyType that will modify existing constructors to add an IInterceptor[] parameter (along with a constructor without parameters). Then interceptors declared in InterceptedBy are passed to the subtype.

When you add the UsingConstructor method, Autofac will create a subtype using a parameterless constructor that will not register declared hooks. That is why you do not have the expected behavior. This can be done by forcing Autofac to allow the newly signed constructor. Like this:

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

This will work as intended. This behavior frankly surprises me, I would think that the interceptors would be added after building the Autofac / DynamicProxy in the second stage. I am wondering if this can be seen as a mistake or at least amazing behavior. The solution above smells a bit, since it is not at all obvious that you need to change the signature of the constructor in order to host the proxy.

If you need to use a constructor with parameters, the IInterceptor[] parameter is always added first in inherited constructors, for example, using the int based constructor you write: .UsingConstructor(new Type[2] {typeof(IInterceptor[]), typeof(int) })


I think there may be a problem with the method that you declare as virtual and / or you want to intercept: here is some test code that shows that the interceptor is being called against the interface and class:

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

As you can see, the second registration calls the interceptor in all cases:

  • interface resolution and calling an interface method
  • class resolution and invocation of an interface method
  • class resolution and class method call

Are you sure the registration is correct? What do you allow in the second case ( IMyType or MyType )?

+2
source share

When using EnableClassInterceptors(), Autofac tells Castle Dynamic Proxy to subclass your type. This new subtype receives new constructor signatures that add a parameter of type IInterceptor[] . Meanwhile, by default Autofac uses MostParametersConstructorSelector to select the constructor to use when activating the type. Therefore, it will usually correspond to the constructor with the parameter IInterceptor[] .

When you call UsingConstructor() , the registration is changed using MatchingSignatureConstructorSelector corresponding to the parameter types you specify. (In your case, no.) This leads to the fact that Autofac does not use a constructor that accepts the IInterceptor[] parameter, so you should not pass interceptors to the proxy server.

I am really surprised that it does not throw an exception due to the lack of an appropriate constructor, because what the code looks like should happen. This part has not yet been resolved.

+5
source share

All Articles