Search code examples
c#ninjectaopcastle-dynamicproxyninject-interception

Ninject interception proxying class with non empty constructor via castle dynamic proxy


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).


Solution

  • 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