Хорошо, вы сделали хороший первый шаг, признав, что Web.config - это просто другая зависимость и его упаковка в ConfigProvider для инъекций - отличное решение.
Но вы попадаете в одну из проблем дизайна MVC, а именно, чтобы быть дружественными к DI, атрибуты должны предоставлять только метаданные, но never actually define behavior. Это не проблема с вашим подходом к тестированию, это проблема с подходом к дизайну фильтра.
Как указано в сообщении, вы можете обойти эту проблему, разделив атрибут фильтра действий на 2 части.
- Атрибут, который не содержит поведения, чтобы помечать ваши контроллеры и методы действий с помощью.
- DI-friendly класс, который реализует IActionFilter и содержит желаемое поведение.
Подход заключается в использовании IActionFilter для проверки наличия атрибута и последующего выполнения желаемого поведения. Фильтр действий может поставляться со всеми зависимостями и затем вводиться, когда приложение составлено.
IConfigProvider provider = new WebConfigProvider();
IActionFilter filter = new MaxLengthActionFilter(provider);
GlobalFilters.Filters.Add(filter);
ПРИМЕЧАНИЕ: Если вам нужна зависимостей фильтра, чтобы иметь продолжительность жизни короче, чем одноплодной, вам нужно будет использовать GlobalFilterProvider
как в this answer.
Реализация MaxLengthActionFilter будет выглядеть примерно так:
public class MaxLengthActionFilter : IActionFilter
{
public readonly IConfigProvider configProvider;
public MaxLengthActionFilter(IConfigProvider configProvider)
{
if (configProvider == null)
throw new ArgumentNullException("configProvider");
this.configProvider = configProvider;
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);
if (attribute != null)
{
var maxLength = attribute.MaxLength;
// Execute your behavior here, and use the configProvider as needed
}
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
var attribute = this.GetMaxLengthAttribute(filterContext.ActionDescriptor);
if (attribute != null)
{
var maxLength = attribute.MaxLength;
// Execute your behavior here, and use the configProvider as needed
}
}
public MaxLengthAttribute GetMaxLengthAttribute(ActionDescriptor actionDescriptor)
{
MaxLengthAttribute result = null;
// Check if the attribute exists on the controller
result = (MaxLengthAttribute)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(typeof(MaxLengthAttribute), false)
.SingleOrDefault();
if (result != null)
{
return result;
}
// NOTE: You might need some additional logic to determine
// which attribute applies (or both apply)
// Check if the attribute exists on the action method
result = (MaxLengthAttribute)actionDescriptor
.GetCustomAttributes(typeof(MaxLengthAttribute), false)
.SingleOrDefault();
return result;
}
}
И ваш атрибут , который не должен содержать какое-либо поведение должен выглядеть примерно так:
// This attribute should contain no behavior. No behavior, nothing needs to be injected.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MaxLengthAttribute : Attribute
{
public MaxLengthAttribute(int maxLength)
{
this.MaxLength = maxLength;
}
public int MaxLength { get; private set; }
}
С более слабо связанный дизайн, тестирование на наличие атрибута гораздо более прямолинейно.
[TestMethod]
public void Base_controller_must_have_MaxLengthFilter_attribute()
{
var att = typeof(BaseController).GetCustomAttribute<MaxLengthAttribute>();
Assert.IsNotNull(att);
}
Спасибо @ NightOwl888! Это действительно ответ, на который я могу погрузить свои зубы - и легко понять. Я всегда соглашался с тем, что атрибуты не должны определять поведение, но мое ограниченное время для интроспекции заставило меня сделать исключение для атрибутов фильтра действий. – ProfK