3

I am basing most of my current implementation off the information provided here:

Ninject Intercept any method with certain attribute?

I use a custom planning strategy class which looks for all methods with given attributes (not ninject interceptor attributes) which will then get proxied if it matches the criteria.

An example of usage would be:

Kernel.Components.Add<IPlanningStrategy, CustomPlanningStrategy<LoggingAttribute, LoggerInterceptor>>();

This would then look for any methods which have a [Logging] attribute and will then use the logging interceptor.

However I am currently getting InvalidProxyConstructorArgumentsException from dynamic proxy when it is trying to proxy the methods with related attributes on. Now I remember reading that you need virtual methods, however I do not remember seeing that you HAD to have a parameterless constructor.

All bindings are done against interfaces, and the AOP interceptors happen via attributes and the custom proxy planning class mentioned in the link above.

So is there a way to get dynamic proxy (or the linfu version) to proxy the classes which have constructors with dependencies? (All dependencies are in the Kernel so its not like they cannot be resolved).

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Grofit
  • 17,693
  • 24
  • 96
  • 176
  • Please be specific. What are your bindings? What's your interceptor implementation? Are you using the custom `IPlanningStrategy` or simply `.Intercept().With()`? – BatteryBackupUnit Mar 12 '14 at 11:22
  • The link above details it, I use attributes (which do not inherit from `InterceptorAttribute`) and then use a custom planning strategy class to go through classes and check for methods which have the attributes and then proxies it. Will update the main question with an example. – Grofit Mar 12 '14 at 12:21

2 Answers2

4

Looking at the proxy generating code: https://github.com/ninject/ninject.extensions.interception/blob/master/src/Ninject.Extensions.Interception.DynamicProxy/DynamicProxyProxyFactory.cs

    if (targetType.IsInterface)
        {
            reference.Instance = this.generator.CreateInterfaceProxyWithoutTarget(targetType, additionalInterfaces, InterfaceProxyOptions, wrapper);
        }
        else
        {
            object[] parameters = context.Parameters.OfType<ConstructorArgument>()
                .Select(parameter => parameter.GetValue(context, null))
                .ToArray();
            reference.Instance = this.generator.CreateClassProxy(targetType, additionalInterfaces, ProxyOptions, parameters, wrapper);
        }

one can see that ninject's dynamic proxy extension is only passing ConstructorArguments to the Castle Dynamic Proxy Generator.

So - without changes to the ninject extension or creating your own - you need to pass all dependencies as constructor arguments. You could also try out whether property / method injection works (see https://github.com/ninject/ninject/wiki/Injection-Patterns).

If you control the code you could add interfaces to the proxied classes and then use an "interface proxy with target". This allows to decouple proxy instantiation from target (proxied class) instantiation --> target can have dependencies ctor injected without any changes to ninject (-extensions).

Clarification: Having the following class which should be proxied:

public interface IBar { }

public class Foo 
{
     public Foo(IBar bar)
     {
     }
}

And the following binding:

Bind<Foo>().ToSelf().Intercept().With<SomeInterceptor>();
Bind<IBar>().To<Bar>();

And then retrieving a Foo from the ninject container:

IResolutionRoot.Get<Foo>();

won't work.

Putting all constructor arguments on the ninject context to make it work

However, we can change the retrieving of Foo to make it work:

var bar = IResolutionRoot.Get<IBar>();
IResolutionRoot.Get<Foo>(new ConstructorArgument("bar", bar);

Now this is suboptimal because ninject is not doing dependency resolution automatically.

Adding interface to proxied class to make it work better

We can work around the issue by using a "interface proxy with target". First, we add an interface to the proxied class:

public interface IFoo{ }

public class Foo : IFoo
{
     public Foo(IBar bar)
     {
     }
}

And then we change the binding to:

Bind<IFoo>().To<Foo>().Intercept().With<SomeInterceptor>();

And then retrieving a Foo from the ninject container:

IResolutionRoot.Get<Foo>();

works.

Another, possibly easier (&uglier?) solution According to @Daniel this works: Add two constructor to the proxied type:

  • one protected constructor without parameters. This one is for DynamicProxy to create the proxy.
  • one public/internal constructor with the arguments, to be used by ninject to instantiate the proxied type.

Ninject will automatically pick the constructor with the most arguments it can resolve.

BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • Ah ok so if there are any constructors which have ambiguous dependencies it wont know what to do and blows up. – Grofit Mar 12 '14 at 11:56
  • maybe i'm nitpicking / maybe i'm not understanding you correctly: Every constructor argument the proxied-class takes must be available on the ninject context as a parameter of type `ConstructorArgument`. There is no dependency resolution for the proxied class. **Except** when you use "interface with target proxy": then the class is instantiated "as usual" with dependency resolution and only the interface-proxy is instantiated without dependency resolving - it does not require it anyway. – BatteryBackupUnit Mar 12 '14 at 12:03
  • Just to clarify, everything goes through Ninject, so all classes and constructor arguments are within the dependency tree. There are no manual `.WithConstructorArgument("someArg", someObj)` as everything is in the tree so gets populated, and the classes themselves work fine without AOP involved. It is just the moment that I try to hook up the attribute interception shown above it cannot proxy the classes, I assume after thinking on your answer it may be because some constructor may have an ambiguous argument, like arguments which share the same interface but have 2 different implementations. – Grofit Mar 12 '14 at 13:28
  • AFAIK: If there are no `.WithConstructorArgument(..)` but the proxy needs a constructor parameter, it will fail. Because ninject does not do constructor injection for the proxied class of a "class proxy without target". Even if the dependency is unambiguous, it will fail. – BatteryBackupUnit Mar 12 '14 at 14:46
  • This is what I was afraid of, I was under the impression from other articles that it passed the parameters to castle but in no sort of order, so if castle could not decide the type needed it would blow up. It seems a very big thing if *ALL* AOP'd classes have to have parameterless constructors as the main reason you use an IoC framework is to handle your constructor dependencies in an elegant way, so if you cannot AOP whilst using DI it seems to mean it is only useful on classes without dependencies, which probably wont be DIed so would not be intercepted anyway... sad panda face... – Grofit Mar 12 '14 at 15:23
  • However, if you add an interface to the proxied class, it **will** work. Dependency resolution is only missing for class proxies, not for "interface with target" proxies. If you are doing Unit Testing / TDD you are most likely using interfaces all over anyway, so this feature was probably very low prio. Maybe class proxy dependency injection works with the Linfu proxy extension, i dont know. – BatteryBackupUnit Mar 12 '14 at 15:37
  • This is what I find odd, everything is bound as an interface to a concrete class, i.e. `Bind().To()`. However I do not know how to tell it to proxy at the interface level rather than the class level, but as I am using this custom attribute driven approach to proxying there is very little information on how to go about doing it so I can proxy interfaces. I originally thought this would be a more widely done thing than it is, I seem to be the only person trying to do this sort of thing, I like to think of it as MVC ActionFilters for applications. – Grofit Mar 12 '14 at 16:21
  • 1
    What could be done relatively easily, is use the convention extension to find all classes with 1+ methods with the `[Logging]` attribute and then bind them to their interface and "interception with". – BatteryBackupUnit Mar 13 '14 at 07:47
  • Could you provide an example of how to do this? as I have tried searching for this but could not find a way to do it. I expected it would be via the lambda Intercept extensions but there are no examples and I am not sure if it will infer the interface or class proxy itself or if I need to hint/tell it to do one or the other. – Grofit Mar 13 '14 at 08:38
  • The last solution works and the parameterless constructor doesn't even need to be public. It can simply be protected. – Daniel B Apr 14 '15 at 23:45
3

An alternate approach would be to use a convention based binding for all classes with a method with a [Logging] attribute. However, this means that adding a [Logging] attribute to a method will influence the binding of the object, which may be undesired.

So this is how it would work (verified to work):

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class LoggingAttribute : Attribute
{
}

public interface IClassNotToBeIntercepted
{
    void DoSomething();
}

public class ClassNotToBeIntercepted : IClassNotToBeIntercepted
{
    public void DoSomething() { }
}

public interface IClassToBeIntercepted
{
    void DoNotLogThis();
    void LogThis();
    void LogThisAsWell();
}

public class ClassToBeIntercepted : IClassToBeIntercepted
{
    public void DoNotLogThis() { }

    [Logging]
    public void LogThis() { }

    [Logging]
    public void LogThisAsWell() { }
}

public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.WriteLine("interceptor before {0}", BuildLogName(invocation));

        invocation.Proceed();

        Console.WriteLine("interceptor after {0}", BuildLogName(invocation));
    }

    private static string BuildLogName(IInvocation invocation)
    {
        return string.Format(
            "{0}.{1}", 
            invocation.Request.Target.GetType().Name,
            invocation.Request.Method.Name);
    }
}

public class DemoModule : NinjectModule
{
    public override void Load()
    {
        this.Bind(convention => convention
            .FromThisAssembly()
            .SelectAllClasses()
            .Where(ContainsMethodWithLoggingAttribute)
            .BindDefaultInterface()
            .Configure(x => x
                .Intercept()
                .With<LoggingInterceptor>()));

        this.Bind<IClassNotToBeIntercepted>()
            .To<ClassNotToBeIntercepted>();
    }

    private static bool ContainsMethodWithLoggingAttribute(Type type)
    {
        return type
            .GetMethods()
            .Any(method => method.HasAttribute<LoggingAttribute>());
    }
}

And a test:

    [Fact]
    public void InterceptorTest()
    {
        var kernel = new StandardKernel();
        kernel.Load<DemoModule>();

        kernel.Get<IClassNotToBeIntercepted>()
            .DoSomething();

        kernel.Get<IClassToBeIntercepted>()
            .LogThis();
    }

Results in the following console output:

interceptor before ClassToBeIntercepted.LogThis
interceptor after ClassToBeIntercepted.LogThis
BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • This seems to be an interesting way to solve the problem, will flag this as the answer as this gives us the separation required and also allows us to configure the interceptors for each attribute. Thank you for spending the time to educate me and others :) – Grofit Mar 13 '14 at 10:50