Unity: зарегистрируйте два интерфейса как один синглтон с перехватом
У меня есть класс, который реализует два интерфейса, и я хочу применить перехват к методам класса.
Я следую совету в Unity Register два интерфейса как один синглтон, но результаты меня удивляют. В двух словах, кажется, что мой 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, приводит к тому, что журнал появляется три раза.
Я ожидал, что журнал будет вызван только один раз. Вероятно, я могу добиться этого, если LogCallHandler обнаружит, вызывается ли он из другого LogCallHandler, но это выглядит не элегантно.
Первоначально я хотел применить поведение перехвата к C, а не к I1 и I2 по отдельности, но для этого требуется, чтобы C наследовал от MarshalByRefObject, что является ограничением, которое я пока не готов навязывать.
Есть ли альтернативный способ?
2 ответа
Проблема в том, что вы применяете прозрачный прокси для каждого интерфейса. Вместо этого, если вы примените его к конкретному классу, вы получите только один прокси. Кроме того, вам не нужно делать его одноэлементным, если вы не хотите, чтобы экземпляр делился.
Я запустил эту конфигурацию в тестовом консольном проекте и получил желаемый результат. Престижность за включение рабочего фрагмента, который изолировал вашу проблему!
var container = new UnityContainer()
.AddNewExtension<Interception>()
.RegisterType<I1, C>()
.RegisterType<I2, C>()
.RegisterType<C>(
new ContainerControlledLifetimeManager(),
new Interceptor<TransparentProxyInterceptor>(),
new InterceptionBehavior<PolicyInjectionBehavior>()
);
Оказывается, небольшая модификация моего оригинального фрагмента дает решение:
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();
}
Единственная разница в том, что я установил перехват для I1 и I2 вне вызова RegisterType. Результат вышеупомянутого:
Entering Method1
Method1 0
Leaving Method1
Entering Method2
Method2 1
Leaving Method2
Method2 2
Что дает мне следующее:
- Я перехватываю реализацию C как I1, так и I2.
- Существует только один экземпляр C (о чем свидетельствует изменение члена Data).
- Я могу добраться до единственного экземпляра C (т.е.
container.Resolve<C>()
работает, правда, без перехвата).
Мой пример использования здесь - модульные тесты: мой код полагается на перехват некоторых его функций (в частности, управление транзакциями). C - это фиктивный объект, который я предоставляю своему тестируемому классу, и он реализует два интерфейса, которые нужны тестируемому классу. Когда запускается юнит-тест, я хочу получить доступ к фиктивному объекту, чтобы проверить, что на нем есть.
Недостатком вышеприведенного решения является то, что я могу применить только PolicyInjectionBehavior здесь (на самом деле, код выше не определяет никакого InterceptionBehavior: при использовании container.Configure<Interception>
насколько я могу судить, PolicyInjectionBehavior автоматически используется Unity. Для моего случая использования это приемлемо.
Я должен признать, что я удивлен, что настройка перехвата во время RegisterType дает другие результаты, чем настройка его отдельно. Если кто-нибудь может объяснить это, я был бы рад понять, почему это так.