How to get Interceptors working for classes with multi-arg constructors

45 views
Skip to first unread message

Michael Hart

unread,
Jun 19, 2008, 12:52:49 AM6/19/08
to nin...@googlegroups.com
So I'm playing around with the interceptors and trying to get some
logging happening - but I can't figure out how to get going.

It seems like the problem I'm having is something to do with LinFu/
DynamicProxy2 not being able to call a constructor with arguments -
even if there's no interception on the object in question.

Here's a simple test case to show what I'm trying to do (and fails
with both LinFu and DynamicProxy2 but not with DummyProxyFactory):

public class SomeObject
{
}

public class SomeOtherObject
{
public readonly SomeObject someObj;
public SomeOtherObject(SomeObject someObj)
{
this.someObj = someObj;
}
}

public class SomeInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
}
}

[Test]
public void Whatever()
{
var module = new InlineModule(
m => m.Bind<SomeObject>().ToSelf(),
m => m.Bind<SomeOtherObject>().ToSelf(),
// Doesn't matter what condition I put here
m =>
m
.Intercept<SomeInterceptor>(When.Request.Method.Name.StartsWith("asfd"))
);

using (var kernel = new StandardKernel(
// LinFuModule also fails,
new DynamicProxy2Module(),
module))
{
var obj = kernel.Get<SomeOtherObject>();

Assert.That(obj, Is.Not.Null);
Assert.That(obj.someObj, Is.Not.Null);
}
}


Any ideas as to what I'm doing wrong?

Cheers,

Michael

Nate Kohari

unread,
Jun 19, 2008, 10:03:09 AM6/19/08
to nin...@googlegroups.com
Michael:

What's actually happening here is that Ninject activates the type first, and then wraps it in a proxy. The result of this is that you're actually testing the wrong field. What you should do instead is create a virtual property which exposes the someObj dependency, and then test against that.

I'll readily admit it's confusing the way this works, but it's largely due to the way the proxy factories have to create types -- if they can't override the implementation of a given member, they will just create a new one that hides it. In order to make something interceptable, it has to either be part of an interface, or it needs to be marked as virtual so it can be overridden.

I might go back and see if I can make this a little more user-friendly. :)

(By the way, thanks for filing the bugs! Keep them coming if you find any others.)


Thanks,
Nate

Michael Hart

unread,
Jun 19, 2008, 8:12:37 PM6/19/08
to nin...@googlegroups.com
Hmmm, thanks for the clarification - two issues - firstly, I can't get
DynamicProxy2 to find the constructor at all, regardless of whether I
use virtual properties or not. If you could post some example code for
how to turn the test I posted into a working version with
DynamicProxy2, that'd be great.

Secondly, LinFu finds the constructor ok, but what threw me was that
the inclusion of an interceptor that won't even activate on any object
suddenly broke all of my existing intercepted objects because their
private fields were suddenly null.

So I guess if we want to include even just one teensy weensy little
interceptor in a single object, we have to change our entire injected
codebase to use public virtual properties? Even for objects that
previously didn't expose any public fields?

That's rather unfortunate!

Let me know if my thinking is right on this.

Cheers,

Michael

On 20/06/2008, at 12:03 AM, Nate Kohari wrote:

> Michael:
>
> What's actually happening here is that Ninject activates the type
> first, and then wraps it in a proxy. The result of this is that
> you're actually testing the wrong field. What you should do instead
> is create a virtual property which exposes the someObj dependency,
> and then test against that.
>
> I'll readily admit it's confusing the way this works, but it's
> largely due to the way the proxy factories have to create types --
> if they can't override the implementation of a given member, they
> will just create a new one that hides it. In order to make something
> interceptable, it has to either be part of an interface, or it needs
> to be marked as virtual so it can be overridden.
>
> I might go back and see if I can make this a little more user-
> friendly. :)
>
> (By the way, thanks for filing the bugs! Keep them coming if you
> find any others.)
>
>
> Thanks,
> Nate
>

> On Thu, Jun 19, 2008 at 12:52 AM, Michael Hart <michael...@gmail.com

Michael Hart

unread,
Jun 19, 2008, 8:24:58 PM6/19/08
to Michael Hart, nin...@googlegroups.com
Actually - I've just realised that I was overstating it a little.

You don't have to change all of your private fields to be public
virtual properties - just any (public? protected?) methods that access
those fields. Is that correct?

Still a bit disconcerting to have to change all non-intercepted
classes too - but I guess there's no real way for Ninject to know
ahead of time which objects will be intercepted or not, so all
injected objects are proxied - is that right?

Michael

Nate Kohari

unread,
Jun 19, 2008, 9:48:09 PM6/19/08
to nin...@googlegroups.com
Michael:

Yes, and this is really a limitation of the way dynamic proxying support works on the .NET framework. Without direct language or runtime support, methods can only be proxied if they are virtual, or if you interact with them via an interface. This unfortunate limitation is shared by all runtime AOP systems for .NET that don't get into more exotic things like IL weaving and the like.

The main reason why you're seeing this behavior is that you're using dynamic interception via the fluent interface in your module. Because dynamic interception examines the context of the method call itself, you're right -- Ninject has no way of knowing which types would be intercepted, so it has to wrap everything in a proxy.

However, bear in mind this is only true with *dynamic* interception. If you instead use static interception via the InterceptAttribute, only the type that is decorated will be proxied.

I believe that the problem that you're seeing is that your test is actually reading the field defined in the *proxy*, whereas the constructor is setting the field that is defined in the *actual* instance. Changing it to a private field wrapped in a public virtual property will provide a means to get at the field defined inside the actual instance from outside the proxy, and should solve your problem.


-Nate

>> On Thu, Jun 19, 2008 at 12:52 AM, Michael Hart <michael.hart.au@gmail.com

Michael Hart

unread,
Jun 20, 2008, 12:40:02 AM6/20/08
to nin...@googlegroups.com
Well Lin Fu does delve into this more exotic path of IL weaving
(LinFu.AOP.Weavers.Cecil) - it's probably something you've already
looked at, but how tricky a task do you think it would be to integrate
this with Ninject? (I'm guessing most of the heavy lifting has been
done by LinFu)

Michael

On 20/06/2008, at 11:48 AM, Nate Kohari wrote:

> Michael:
>


> Yes, and this is really a limitation of the way dynamic proxying
> support works on the .NET framework. Without direct language or
> runtime support, methods can only be proxied if they are virtual, or
> if you interact with them via an interface. This unfortunate
> limitation is shared by all runtime AOP systems for .NET that don't
> get into more exotic things like IL weaving and the like.
>
> The main reason why you're seeing this behavior is that you're using
> dynamic interception via the fluent interface in your module.
> Because dynamic interception examines the context of the method call
> itself, you're right -- Ninject has no way of knowing which types
> would be intercepted, so it has to wrap everything in a proxy.
>
> However, bear in mind this is only true with *dynamic* interception.
> If you instead use static interception via the InterceptAttribute,
> only the type that is decorated will be proxied.
>
> I believe that the problem that you're seeing is that your test is
> actually reading the field defined in the *proxy*, whereas the
> constructor is setting the field that is defined in the *actual*
> instance. Changing it to a private field wrapped in a public virtual
> property will provide a means to get at the field defined inside the
> actual instance from outside the proxy, and should solve your problem.
>
>
> -Nate
>

> On Thu, Jun 19, 2008 at 8:24 PM, Michael Hart <michael...@gmail.com

> >> On Thu, Jun 19, 2008 at 12:52 AM, Michael Hart <michael...@gmail.com

Nate Kohari

unread,
Jun 20, 2008, 7:27:41 AM6/20/08
to nin...@googlegroups.com
Michael:

The main issue with IL weaving is that it has to be done at compile time. The main reason AOP is typically integrated with dependency injection frameworks is that the framework already has an activation pipeline that the instance is being passed through as it is created and injected. From there, it's a simple step to wrap it in a proxy.

IL weaving, in contrast, doesn't really fit with a dependency injection framework, since it's just an additional step in the build process. After you compile your code, you run the IL weaver, and it alters the bytecodes that were outputted from the compiler. After that, the interception logic is literally part of your code itself, and you don't need any outside interaction to make it work.

If you're interested in another take on AOP, IL weaving is pretty cool stuff, and it can work in conjunction with a dependency injection framework without the framework actually supporting it directly.


-Nate

> On Thu, Jun 19, 2008 at 8:24 PM, Michael Hart <michael.hart.au@gmail.com
> >> On Thu, Jun 19, 2008 at 12:52 AM, Michael Hart <michael.hart.au@gmail.com

Michael Hart

unread,
Jun 20, 2008, 7:47:09 AM6/20/08
to nin...@googlegroups.com
Yep, I realise the compile step is necessary, but I was just wondering
if there was a way for Ninject to (transparently?) support both
options - the proxy method, and the IL weaving method. There's a lot
of niceties in the activation syntax of Ninject (props are due here!)
- and if this could be leveraged to deliver AOP with the IL weaving
step as it already does by proxies, and also to eliminate having to
learn another project's syntax, that'd be even better! The primary
motivation for me obviously being that I wouldn't have to
"artificially" modify any of my existing codebase, yet could still use
Ninject's interceptors as I already do for injection.

I haven't delved into any of the concepts deeply enough to figure out
what issues there are surrounding this - I'm basically going off this:

http://www.codeproject.com/KB/cs/LinFuPart6.aspx

And thinking that the LinFu classes/interfaces mentioned there could
be supported somehow by the wraparound interception framework already
in Ninject. Who knows - maybe they already can? I'm not in a position
to check at the moment.

Does this make sense?

Michael

On 20/06/2008, at 9:27 PM, Nate Kohari wrote:

> Michael:
>
> The main issue with IL weaving is that it has to be done at compile
> time. The main reason AOP is typically integrated with dependency
> injection frameworks is that the framework already has an activation
> pipeline that the instance is being passed through as it is created
> and injected. From there, it's a simple step to wrap it in a proxy.
>
> IL weaving, in contrast, doesn't really fit with a dependency
> injection framework, since it's just an additional step in the build
> process. After you compile your code, you run the IL weaver, and it
> alters the bytecodes that were outputted from the compiler. After
> that, the interception logic is literally part of your code itself,
> and you don't need any outside interaction to make it work.
>
> If you're interested in another take on AOP, IL weaving is pretty
> cool stuff, and it can work in conjunction with a dependency
> injection framework without the framework actually supporting it
> directly.
>
>
> -Nate
>

> On Fri, Jun 20, 2008 at 12:40 AM, Michael Hart <michael...@gmail.com

> > On Thu, Jun 19, 2008 at 8:24 PM, Michael Hart <michael...@gmail.com

> > >> On Thu, Jun 19, 2008 at 12:52 AM, Michael Hart <michael...@gmail.com

Nate Kohari

unread,
Jun 20, 2008, 8:14:18 AM6/20/08
to nin...@googlegroups.com
Michael:

The main problem is that you can't weave IL at runtime, and Ninject is geared totally around wiring things at runtime (which is what you need to set up dependency injection). If there was a way to weave IL at runtime, I'd be all over it, but the .NET JITter doesn't provide the necessary hooks unless you're willing to hook into the profiler API, which is all unmanaged code and is generally not designed for production apps.

That being said, I haven't looked for awhile, so maybe I'll give LinFu and Cecil another glance and see if they've come up with some sort of wizardry. :)


-Nate

> On Fri, Jun 20, 2008 at 12:40 AM, Michael Hart <michael.hart.au@gmail.com
> > On Thu, Jun 19, 2008 at 8:24 PM, Michael Hart <michael.hart.au@gmail.com
> > >> On Thu, Jun 19, 2008 at 12:52 AM, Michael Hart <michael.hart.au@gmail.com

Michael Hart

unread,
Jun 20, 2008, 10:02:31 AM6/20/08
to nin...@googlegroups.com
I wasn't thinking that the weaving be done at runtime - but rather the
setup of the interceptors be done then. AFAICT, the compile-time
behaviour of LinFu.AOP turns every type you tell it to into an
IModifiableType, and then you check for this in the creation of your
interceptors at runtime.

So the weaving is just done to prepare the classes for, well, whatever
you want - just like turning them into proxies. Then the behaviour is
all specified at runtime.

Do a search in the linked article for "DynamicProxy Compatibility" and
see if it sounds like the two could co-exist with Ninject - it'd be
great if they could!

Cheers,

Michael

On 20/06/2008, at 10:14 PM, Nate Kohari wrote:

> Michael:
>


> The main problem is that you can't weave IL at runtime, and Ninject
> is geared totally around wiring things at runtime (which is what you
> need to set up dependency injection). If there was a way to weave IL
> at runtime, I'd be all over it, but the .NET JITter doesn't provide
> the necessary hooks unless you're willing to hook into the profiler
> API, which is all unmanaged code and is generally not designed for
> production apps.
>
> That being said, I haven't looked for awhile, so maybe I'll give
> LinFu and Cecil another glance and see if they've come up with some
> sort of wizardry. :)
>
>
> -Nate
>

> On Fri, Jun 20, 2008 at 7:47 AM, Michael Hart <michael...@gmail.com

> > On Fri, Jun 20, 2008 at 12:40 AM, Michael Hart <michael...@gmail.com

> > > On Thu, Jun 19, 2008 at 8:24 PM, Michael Hart <michael...@gmail.com

> > > >> On Thu, Jun 19, 2008 at 12:52 AM, Michael Hart <michael...@gmail.com

Nate Kohari

unread,
Jun 20, 2008, 1:06:49 PM6/20/08
to nin...@googlegroups.com
Michael:

Hmm... very cool! I haven't seen LinFu.AOP before, only DynamicProxy. I'll give this a closer look. Thanks for pointing it out, it'd be pretty sweet if I could get this hooked into the Ninject pipeline.

-Nate

> On Fri, Jun 20, 2008 at 7:47 AM, Michael Hart <michael.hart.au@gmail.com
> > On Fri, Jun 20, 2008 at 12:40 AM, Michael Hart <michael.hart.au@gmail.com
> > > On Thu, Jun 19, 2008 at 8:24 PM, Michael Hart <michael.hart.au@gmail.com
> > > >> On Thu, Jun 19, 2008 at 12:52 AM, Michael Hart <michael.hart.au@gmail.com

Michael Hart

unread,
Jun 24, 2008, 8:36:11 PM6/24/08
to nin...@googlegroups.com
OK, I've decided to suck it up and put my, er, code where my mouth is.

I've created a LinFuAopModule that connects IProxyFactory to a
LinFuAopProxyFactory to activate any objects that implement
IModifiableType - that is, any objects that have been "postwoven" by
LinFu.AOP.

This means that there is no need to use DynamicProxies to enable
interception, and all my classes can stay as is, oh joy. The only
issues I've faced so far have been problems with LinFu.AOP Postweaver
working on certain large projects - however, this is unrelated to the
Ninject module.

So here's the code - I've copied it inline because I wasn't sure
whether the attachment would get through. It's all in one file and not
particularly well documented - it'd be good to get some feedback as to
whether my approach is usable or not, and if so, I'm happy to clean it
up a bit.

----8<----

using LinFu.AOP.Interfaces;
using Ninject.Core;
using Ninject.Core.Activation;
using Ninject.Core.Infrastructure;
using Ninject.Core.Interception;

namespace Ninject.Contrib.LinFuAop
{
public class LinFuAopModule : StandardModule
{
public override void BeforeLoad()
{
// We will use a method replacement strategy that checks
// first whether the method should be replaced
// The other option is to use
SimpleMethodReplacementProviderFactory
// which will always try to replace the method

Kernel.Components.Connect<IMethodReplacementProviderFactory>(
new CheckingMethodReplacementProviderFactory());

Kernel.Components.Connect<IProxyFactory>(new
LinFuAopProxyFactory());
}

public override void Load()
{
}
}

public class LinFuAopProxyFactory : ProxyFactoryBase
{
public override object Wrap(IContext context, object instance)
{
IModifiableType modified = instance as IModifiableType;
if (modified != null)
{
modified.IsInterceptionEnabled = true;
modified.MethodReplacementProvider =

Kernel.Components.Get<IMethodReplacementProviderFactory>()
.Create(Kernel, context, instance);
}
// Could add an else check in here and wrap in a
DynamicProxy...
return instance;
}

public override object Unwrap(IContext context, object proxy)
{
IModifiableType modified = proxy as IModifiableType;
if (modified != null)
{
modified.IsInterceptionEnabled = false;
modified.MethodReplacementProvider = null;
}
return proxy;
}
}

public interface IMethodReplacementProviderFactory :
IKernelComponent
{
IMethodReplacementProvider Create(IKernel kernel, IContext
context, object instance);
}

// Creates a MethodReplacementProvider that checks whether it
should
// replace the method or not based on the Ninject context/request
public class CheckingMethodReplacementProviderFactory :
KernelComponentBase, IMethodReplacementProviderFactory
{
public IMethodReplacementProvider Create(IKernel kernel,
IContext context, object instance)
{
return new CheckingMethodReplacementProvider(kernel,
context, instance);
}
}

// Uses LinFu's inbuilt SimpleMethodReplacementProvider and will
always
// try to replace the method
public class SimpleMethodReplacementProviderFactory :
KernelComponentBase, IMethodReplacementProviderFactory
{
public IMethodReplacementProvider Create(IKernel kernel,
IContext context, object instance)
{
return new SimpleMethodReplacementProvider(
new LinFuAopWrapper(Kernel, context, instance));
}
}

// Very similar to the existing LinFuWrapper, just modified for
LinFu.AOP
public class LinFuAopWrapper : StandardWrapper, IMethodReplacement
{
public LinFuAopWrapper(IKernel kernel, IContext context,
object instance)
: base(kernel, context, instance)
{
}

public object Invoke(IInvocationContext context)
{
IRequest request =
Kernel.Components.Get<IRequestFactory>().Create(Context, Instance,
context.TargetMethod, context.Arguments,
context.TypeArguments);

IInvocation invocation = CreateInvocation(request);

invocation.Proceed();

return invocation.ReturnValue;
}
}

// Check whether we have any interceptors for the given context
before
// replacing the method
public class CheckingMethodReplacementProvider :
BaseMethodReplacementProvider
{
public IKernel Kernel { get; set; }
public IContext Context { get; set; }
public object Instance { get; set; }

public CheckingMethodReplacementProvider(IKernel kernel,
IContext context, object instance)
{
Kernel = kernel;
Context = context;
Instance = instance;
}

protected override bool ShouldReplace(IInvocationContext
context)
{
IRequest request =
Kernel.Components.Get<IRequestFactory>().Create(Context, Instance,
context.TargetMethod, context.Arguments,
context.TypeArguments);

IInterceptorRegistry interceptorRegistry =
Kernel.Components.Get<IInterceptorRegistry>();

// Would be nicer to have an
IInterceptorRegistry.HasInterceptors(IRequest)
return interceptorRegistry.GetInterceptors(request).Count
> 0;
}

protected override IMethodReplacement
GetReplacement(IInvocationContext context)
{
return new LinFuAopWrapper(Kernel, Context, Instance);
}
}

}

----8<----

Cheers,

Michael

LinFuAopModule.cs
Reply all
Reply to author
Forward
0 new messages