Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autofac class interception doesn't work in a certain setup

I have an IoC setup with Autofac and use AoP interceptors.

Normally, I use interface interceptors registered like this:

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 apparent in this minimal example), I need one class to be registered and injected also as self (not through interface), so I try:

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

In this setup, the interceptor is never fired. When I inspect the injected dependency in debug, it does seem to be a subclassed proxy (as it should), but its _interceptors private property only contains a single instance of Castle.DynamicProxy.StandardInterceptor, which is obviously not what I configured.

In fact, if I remove AsSelf() it still doesn't intercept, which leads me to a conclusion that either I'm doing something wrong, or class interception simply doesn't work... ?

UPDATE

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

UPDATE

I overlooked this earlier and to simplify skipped an important fact: I am specifying UsingConstructor() in the registration. Now I empirically found out that UsingConstructor() seems to prevent EnableClassInterceptors() from working.

Full registration I'm 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; it's complex code but I put breakpoint at start of public void Intercept(IInvocation invocation) method.

Commenting out the constructor choice makes it work again.

I will award the bounty to anyone who can give me a workaround, or at least a good explanation of why this doesn't work.

UPDATE

With your answers about added constructor parameters, I investigated this aspect and indeed:

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

returns in fact 3 constructors:

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

This happens whether I add UseConstructor() or not. By coincidence, the code didn't throw for me; it would if I specified the other constructor (not the parameterless).

Regardless, I tried the following registration:

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

... and, surprise, surprise! it fails with

No matching constructor exists on type MyType

Digging further, changing the order of those fluent methods solves it ultimately, with the complete working registration being:

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

SUMMARY

I would consider it bad API to have what seems to be fluent API but yet depend significantly on the order of invoking fluent methods. It's even worse that it doesn't actually throw, just fails silently.

I would also consider it bad design (leave alone documentation) to require from users internal knowledge of proxying logic. Ideally, UsingConstructor() should encapsulate in the matching logic the added fact of interceptors. That said, I designed APIs myself and I know it's easy to demand but difficult to deliver certain features.

Anyway, the case is closed and I would like to give hug to all of you. I believe it was Jim Bolla that gave first precise answer that led to the breakthrough, so cookies go to him. Correct me if I'm wrong.

like image 262
Jacek Gorgoń Avatar asked Aug 22 '14 14:08

Jacek Gorgoń


2 Answers

When you use EnableClassInterceptors(), Autofac tells Castle Dynamic Proxy to subclass your type. This new subtype gets new constructor signatures that add a parameter of type IInterceptor[]. Meanwhile, Autofac by default, uses its MostParametersConstructorSelector to pick the constructor to use when activating a type. So normally it will match on the constructor that has the IInterceptor[] parameter.

When you call UsingConstructor(), the registration changes to use a MatchingSignatureConstructorSelector matching the parameter types you specified. (In your case, none.) This causes Autofac to not use the constructor that accepts the IInterceptor[] parameter, thus causing your interceptors to not be passed into the proxy.

I'm actually surprised it doesn't throw an exception for not having a matching constructor, because that's what the code looks like should happen. That part I still haven't solved.

like image 124
Jim Bolla Avatar answered Sep 21 '22 03:09

Jim Bolla


EDIT: the answer by Jim Bolla is right - using EnableClassInterceptors creates a subtype of MyType that will change existing constructors to add a IInterceptor[] parameter (along with a parameterless constructor). Then the interceptors you declare in InterceptedBy are passed to the subtype.

When you add the UsingConstructor method, Autofac will create the subtype using the parameterless constructor which will not register the interceptors declared. That is why you don't have the expected behavior. It is possible to make it work by forcing Autofac to resolve the constructor with the new signature. 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 have thought that interceptors would be added after construction in a second step by Autofac/DynamicProxy. I'm wondering if this could be seen as a bug or at least a surprising behavior. The solution above smells a bit since it is not obvious at all that you have to change the constructor signature in order to accomodate the proxy.

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


I think there may be a problem regarding the method you declare as virtual and/or you want to intercept: here some test code that shows that the interceptor is called against an interface and a 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:

  • resolving by interface and calling the interface method
  • resolving by class and calling the interface method
  • resolving by class and calling the class method

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

like image 45
samy Avatar answered Sep 19 '22 03:09

samy