Cóż, zrobiłeś dobry pierwszy krok, uznając, że Web.config jest po prostu kolejną zależnością i zawijanie go do narzędzia ConfigProvider do wstrzykiwania jest doskonałym rozwiązaniem.
Ale pojawia się problem z jednym z problemów projektowych MVC - mianowicie, że aby być przyjaznym dla DI, atrybuty powinny dostarczać tylko meta-dane, ale never actually define behavior. To nie jest problem z twoim podejściem do testowania, jest to problem związany z podejściem do projektu filtra.
Jak wskazano w poście, można obejść ten problem, dzieląc atrybut filtra działania na dwie części.
- Atrybut, który nie zawiera żadnego zachowania oznaczającego kontrolery i metody działania za pomocą.
- Przyjazna dla DI klasa implementująca IActionFilter i zawierająca pożądane zachowanie.
Podejście polega na użyciu IActionFilter w celu przetestowania obecności atrybutu, a następnie wykonania pożądanego zachowania. Filtr działania może być dostarczany ze wszystkimi zależnościami, a następnie wstrzykiwany po złożeniu aplikacji.
IConfigProvider provider = new WebConfigProvider();
IActionFilter filter = new MaxLengthActionFilter(provider);
GlobalFilters.Filters.Add(filter);
UWAGA: Jeśli potrzebujesz któregokolwiek z zależnościami filtra mieć żywotność krótsza niż Singleton, trzeba będzie użyć GlobalFilterProvider
jak w this answer.
Realizacja MaxLengthActionFilter będzie wyglądać mniej więcej tak:
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;
}
}
A Twój atrybut które nie powinny zawierać żadnych zachowań powinien wyglądać mniej więcej tak:
// 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; }
}
Mając na bardziej luźno powiązany projekt, testowanie istnienia atrybutu jest znacznie prostsze.
[TestMethod]
public void Base_controller_must_have_MaxLengthFilter_attribute()
{
var att = typeof(BaseController).GetCustomAttribute<MaxLengthAttribute>();
Assert.IsNotNull(att);
}
Dziękujemy @ NightOwl888! To jest rzeczywiście odpowiedź, w którą mogę zanurzyć zęby - i łatwo zrozumieć. Zawsze zgadzałem się, że atrybuty nie powinny definiować zachowania, ale mój ograniczony czas na introspekcję zmusił mnie do zrobienia wyjątku dla atrybutów filtra działania. – ProfK