Проблема с передачей функций в параметрах атрибута
Я недавно нашел решение, подобное этому.Параметры для атрибутов должны соответствовать следующим правилам для MS Docs :
Параметры для конструктора атрибутов ограничены простыми типами / литералами: bool, int, double, string,Тип, перечисления и т. Д. И массивы этих типов.Вы не можете использовать выражение или переменную.Вы можете использовать позиционные или именованные параметры.
Из-за этого передача функции фильтру через параметр атрибута - это не то, что мы можем сделать.Вероятно, существует множество альтернатив, но вот что я выбрал:
Решение
Я использовал внедрение зависимостей , чтобы внедрить свой фильтр действий с менеджером, а затемиспользовал Reflection , чтобы указать фильтру, какой метод в менеджере выполнить.Вот как это выглядит:
Класс:
public class Phone : AuditBase
{
...other props...
[AuditRuleset(AuditRule.PhoneNumber)]
public string Number { get; set; }
}
Перечисление:
public enum AuditRule
{
PhoneNumber // You can add [Description] if you want
}
Атрибут:
public class AuditRulesetAttribute : Attribute
{
private readonly AuditRule _rule;
public AuditRulesetAttribute(AuditRule rule) => _rule = rule;
}
Фильтр:
public class FormAuditActionFilter : IActionFilter
{
private ILog _log { get; set; }
private IFormAuditor _auditor { get; set; }
public FormAuditActionFilter(ILog log, IFormAuditor auditor)
{
_log = log;
_auditor = auditor;
}
...lots of filter code...
... The following is from OnActionExecuted, having stored the props of the submitted object in objectProperties...
foreach(PropertyInfo propertyInfo in objectProperties)
{
// Check first for any special audit comparison rules which should be followed
var auditRuleAttributes = propertyInfo.CustomAttributes
.Where(x => x.AttributeType.Name == typeof(AuditRulesetAttribute).Name)
.ToList();
if (auditRuleAttributes.Any())
{
IEnumerable<IList<CustomAttributeTypedArgument>> attrList = auditRuleAttributes
.Select(x => x.ConstructorArguments);
foreach(IList<CustomAttributeTypedArgument> attr in attrList)
foreach(CustomAttributeTypedArgument arg in attr)
if (_auditRuleManager.IsChanged(oldValue, newValue, (AuditRule)arg.Value))
result.Add(BuildValueHistory(propertyInfo.Name, oldValue, newValue));
continue;
}
}
...lots more filter code...
}
AuditRuleManager:
public class AuditRuleManager : IAuditRuleManager
{
public bool IsChanged(object val1, object val2, AuditRule rule)
{
object[] objArray = {val1, val2};
var comparisonResult = typeof(AuditRuleManager)
.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(m => m.Name == rule.GetDescription()) // Try to get description, but falls back to name by default
.Invoke(this, objArray) as bool?;
return (bool)comparisonResult; // Throw an exception if the comparison result was not a valid bool
}
// Compare phone numbers with special rules, and return their equality
private bool PhoneNumber(object val1, object val2) // NOTE: Name of method matches name of enum value
=> GetNumbersFromString(val1 as string) != GetNumbersFromString(val2 as string);
Последним, что мне потребовалось некоторое время, был DI для фильтра, использующего Ninject.Вот как это работает в моем
Global.asax.cs:
kernel.BindFilter<FormAuditActionFilter>(FilterScope.Action, 0)
.WhenActionMethodHas<FormAuditAttribute>()
.WithConstructorArgument("log", log)
.WithConstructorArgument("auditor", auditManager);
Сводка
Вместо передачи функции в качестве параметра атрибута, я использовал DI, чтобы ввестименеджер в мой фильтр.Это дает вашему фильтру доступ к нужным функциям.Во-вторых, я использовал enum для хранения имени функции, которая должна быть выполнена.По сути, все, что вам нужно сделать, чтобы создать новую функцию и выполнить ее с параметром, это:
- Добавить ее в перечисление
- Добавить функцию с тем же именем вменеджер
Надеюсь, это поможет!
Дальнейшее чтение
https://blogs.cuttingedge.it/steven/posts/2014/dependency-injection-in-attributes-dont-do-it/