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.