我有一个实现两个接口的类,我想对类的方法应用拦截。
我正在遵循Unity Register two interfaces as one singleton中的建议,但我对结果感到惊讶。简而言之,似乎我的CallHandler被调用了两次。我有一个最简单的例子:
public interface I1
{
void Method1();
}
public interface I2
{
void Method2();
}
public class C : I1, I2
{
[Log]
public void Method1() {}
public void Method2() {}
}
public class LogAttribute : HandlerAttribute
{
public override ICallHandler CreateHandler(IUnityContainer container)
{
return new LogCallHandler();
}
}
public class LogCallHandler : ICallHandler
{
public IMethodReturn Invoke(
IMethodInvocation input, GetNextHandlerDelegate getNext)
{
Console.WriteLine("Entering " + input.MethodBase.Name);
var methodReturn = getNext().Invoke(input, getNext);
Console.WriteLine("Leaving " + input.MethodBase.Name);
return methodReturn;
}
public int Order { get; set; }
}
void Test()
{
IUnityContainer container = new UnityContainer();
container.AddNewExtension<Interception>();
container.RegisterType<C>(new ContainerControlledLifetimeManager());
container.RegisterType<I1, C>(
new Interceptor<TransparentProxyInterceptor>(),
new InterceptionBehavior<PolicyInjectionBehavior>());
container.RegisterType<I2, C>(
new Interceptor<TransparentProxyInterceptor>(),
new InterceptionBehavior<PolicyInjectionBehavior>());
container.Resolve<I1>().Method1();
}
这给出了这个输出:
Entering Method1
Entering Method1
Leaving Method1
Leaving Method1
删除“container.RegisterType I2,C”行使日志只出现一次。添加第三个接口I3(类似于I2)会导致日志出现三次。
我原本希望只调用一次Log。我可以通过让LogCallHandler检测它是否从另一个LogCallHandler调用来实现这一点,但这看起来不太优雅。
最初我想将拦截行为应用于C而不是分别应用于I1和I2,但这需要C继承MarshalByRefObject,这是我不愿意强加的约束。
还有另一种方法吗?
答案 0 :(得分:5)
问题是您正在将透明代理应用于每个接口。相反,如果将它应用于具体类,则只能获得一个代理。此外,除非您希望共享实例,否则不需要将其设为单例。
我在测试控制台项目中运行此配置并获得了所需的结果。感谢您提供一个隔离问题的工作片段!
var container = new UnityContainer()
.AddNewExtension<Interception>()
.RegisterType<I1, C>()
.RegisterType<I2, C>()
.RegisterType<C>(
new ContainerControlledLifetimeManager(),
new Interceptor<TransparentProxyInterceptor>(),
new InterceptionBehavior<PolicyInjectionBehavior>()
);
答案 1 :(得分:4)
事实证明,对原始代码段的一个小修改提供了一个解决方案:
public interface I1
{
void Method1();
}
public interface I2
{
void Method2();
}
public class C : I1, I2
{
public int Data = 0;
[Log]
public void Method1() { Console.WriteLine("Method1 " + Data); Data = 1; }
[Log]
public void Method2() { Console.WriteLine("Method2 " + Data); Data = 2; }
}
public class LogAttribute : HandlerAttribute
{
public override ICallHandler CreateHandler(IUnityContainer container)
{
return new LogCallHandler();
}
}
public class LogCallHandler : ICallHandler
{
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
Console.WriteLine("Entering " + input.MethodBase.Name);
var methodReturn = getNext().Invoke(input, getNext);
Console.WriteLine("Leaving " + input.MethodBase.Name);
return methodReturn;
}
public int Order { get; set; }
}
void Test()
{
IUnityContainer container = new UnityContainer();
container.AddNewExtension<Interception>();
container.RegisterType<C>(new ContainerControlledLifetimeManager());
container.RegisterType<I1, C>();
container.RegisterType<I2, C>();
container.Configure<Interception>().SetInterceptorFor<I1>(new TransparentProxyInterceptor());
container.Configure<Interception>().SetInterceptorFor<I2>(new TransparentProxyInterceptor());
container.Resolve<I1>().Method1();
container.Resolve<I2>().Method2();
container.Resolve<C>().Method2();
}
唯一的区别是我在调用RegisterType之外设置了I1和I2的拦截。以上的输出是:
Entering Method1
Method1 0
Leaving Method1
Entering Method2
Method2 1
Leaving Method2
Method2 2
这给了我以下内容:
container.Resolve<C>()
可以工作,虽然没有拦截。)我的用例是单元测试:我的代码依赖于拦截它的一些功能(特别是事务管理)。 C是我提供给测试类的模拟对象,它实现了测试类所需的两个接口。当单元测试运行时,我想访问模拟对象以验证其上的内容。
上述解决方案的缺点是我只能在这里应用PolicyInjectionBehavior(实际上,上面的代码没有指定任何InterceptionBehavior:当使用container.Configure<Interception>
时,Unity会自动使用PolicyInjectionBehavior,我可以说。对于我的用例,这是可以接受的。
我不得不承认,我很惊讶在RegisterType期间设置拦截会产生与单独配置不同的结果。如果有人能够解释这一点,我很乐意理解为什么会这样。