Перехват Unity GetCustomAttribute
Заранее спасибо за помощь! (Да, внизу есть вопрос)
Я использую Unity 3.x Interception для выполнения AOP до и после подключения к базе данных и операций транзакций. Перехватчик базы данных всегда создается, а перехватчик транзакции основан на CustomAttributeMatchingRule, оба через InterfaceInterceptor. У меня есть свойства, которые устанавливаются в моем TransactionAttribute:
[Transaction(IsolationLevel.ReadUncommitted, NoRollbackFor = new[] { typeof(TestException) })]
В качестве примера я использую в своем модульном тесте. Я хотел бы получить доступ к ним в моем методе вызова класса TransactionCallHandler. Я видел примеры, говорящие
var transactionAttribute = input.MethodBase.GetCustomAttribute<TransactionAttribute>(false);
это способ получить доступ к этому, но моя транзакция var равна нулю. Мой вывод заключается в том, что прокси-класс перехвата проверяется на наличие специального атрибута, а не исходного конкретного экземпляра.
Моя работа заключается в том, чтобы отразить весь путь обратно до уровня класса, покопаться, выяснить, какой правильный метод перехватывается, и выполнить оттуда пользовательский атрибут get.
var methods = input
.Target
.GetType()
.GetMethods()
.Where(m => m.Name == input.MethodBase.Name)
.Where(m => m.GetCustomAttribute<TransactionAttribute>(false) != null);
(Есть еще около 30 строк кода, чтобы гарантировать, что я не получаю неправильное имя метода, если метод имеет перегрузки; следовательно, снижение производительности...)
Итак, после всего этого мой вопрос: правильно ли я выполняю рефлексию? Есть ли ошибка в Unity, о которой я должен сообщить?
Вот мои определения контейнера:
Container = new UnityContainer();
Container.AddNewExtension<Interception>();
Container.RegisterType<IMockUseDefaultConnectionString, MockUseDefaultConnectionString>(
new InterceptionBehavior<PolicyInjectionBehavior>(),
new Interceptor<InterfaceInterceptor>(),
new InjectionConstructor(new DatabaseSettings()));
Container.RegisterType<IMockUseHardcodedConnectionString, MockUseHardCodedConnectionString>(
new InterceptionBehavior<PolicyInjectionBehavior>(),
new Interceptor<InterfaceInterceptor>(),
new InjectionConstructor(new DatabaseSettings
{
ConnectionString = MockUseHardCodedConnectionString.ConnectionString
}));
/* IDatabaseSettings is not registered to manually control the settings being used */
var first = new InjectionProperty("Order", 1);
var second = new InjectionProperty("Order", 2);
Container
.Configure<Interception>()
.AddPolicy("DatabaseConnectionPolicy")
.AddMatchingRule<NamespaceMatchingRule>(new InjectionConstructor("MyNamespace.*", true))
.AddCallHandler<DatabaseConnectionCallHandler>(first);
Container
.Configure<Interception>()
.AddPolicy("TransactionPolicy")
.AddMatchingRule(new CustomAttributeMatchingRule(typeof(TransactionAttribute), inherited: false))
.AddCallHandler<TransactionCallHandler>(second);
3 ответа
Я думаю, что поведение, которое вы видите, является следствием разработки методов перехвата. При использовании InterfaceInterceptor создается прокси-объект, который реализует целевой интерфейс, однако прокси-объект совершенно другого типа, чем исходный тип.
Если вы используете VirtualMethodInterceptor, который совместим с типом, то вы сможете получить пользовательский атрибут, используя ваш оригинальный подход. Конечно, недостатком VirtualMethodInterceptor является то, что все методы для перехвата должны быть виртуальными.
Другой вариант - создать подкласс из HandlerAttribute. См. Этот SO пост для деталей, или прочитайте этот документ Unity. В вашей реализации CreateHandler() из HandlerAttribute передайте свой экземпляр атрибута вашему экземпляру обработчика вызовов, чтобы вам не приходилось вызывать GetCustomAttributes(). Или реализуйте оба HandlerAttribute и ICallHandler в одном классе и просто верните это:
public sealed class AuditAttribute : HandlerAttribute, ICallHandler
{
#region attribute properties
public string Key { get; set; } // your own attribute properties
public override ICallHandler CreateHandler(Microsoft.Practices.Unity.IUnityContainer container)
{
return this;
}
#endregion
#region ICallHandler
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
// do stuff ...
// then
return getNext()(input, getNext);
}
public int Order { get; set; }
#endregion
}
Я просматривал некоторый пример кода и заметил кое-что довольно простое, о котором я даже не думал.
Когда обработчик вызова создан атрибутом, передайте нужные параметры в конструктор обработчика. Затем они находятся в экземпляре обработчика, который используется для атрибута. Задача решена.