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

人走茶凉 提交于 2019-12-22 11:00:12

问题


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


回答1:


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



回答2:


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.



来源:https://stackoverflow.com/questions/22348731/ninject-interception-proxying-class-with-non-empty-constructor-via-castle-dynami

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!