问题
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 ConstructorArgument
s 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