Я написал ActionFilter, который проверяет длину указанного параметра строки, переданного на любой данный метод действия [Web API], и если длина неверна, задает ActionContext.Response для HttpStatusCode.BadRequest (через вызовите actionContext.Request.CreateErrorResponse()), но я все еще заканчиваю свой код метода действия. Это в основном предназначено для работы, как и все те классы ActionFilterAttribute, которые люди создают для проверки достоверности ModelState вне метода (-ов) действия, но мне также нужна инъекция зависимостей, чтобы я мог использовать регистратор и иметь свой атрибут/ActionFilter можно проверить.Почему не задается действие ActionContext.Response для BadRequest в методе OnActionExecuting(), возвращающем его прямо к вызывающему?
В моем поиске появилось это сообщение в блоге, в котором автор описывает способ иметь «пассивный атрибут» (где атрибут - это в основном DTO) и «сканирующий» ActionFilter, который реализует поведение указанного атрибута. https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=98
Проблема, с которой я сталкиваюсь, заключается в следующем:
(К сожалению, ребята, пожалуйста, медведь со мной. Хотя у меня есть много лет С опытом #, это мой первый реальный forray в атрибут (ы) и/или ActionFilter (s))
Я написал мой атрибут как пассивный (где Атрибут - только DTO) и ActionFilter, который наследует от IActionFilter < CheckStringParamLengthAttribute> как пример (ы) в приведенном выше сообщении блога.
Это мой код атрибута.
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public class CheckStringParamLengthAttribute : Attribute
{
private int _minLength;
private int _maxLength;
private string _errorMessage;
private string _paramName;
public CheckStringParamLengthAttribute(
int MinimumLength,
string parameterName,
string ErrorMessage = "",
int MaximumLength = 0)
{
if (MinimumLength < 0 || MinimumLength > int.MaxValue)
{
throw new ArgumentException("MinimumLength parameter value out of range.");
}
_minLength = MinimumLength;
if (string.IsNullOrEmpty(parameterName))
{
throw new ArgumentException("parameterName is null or empty.");
}
_paramName = parameterName;
// these two have defaults, so no Guard check needed.
_maxLength = MaximumLength;
_errorMessage = ErrorMessage;
}
public int MinLength { get { return _minLength; } }
public int MaxLength { get { return _maxLength; } }
public string ErrorMessage { get { return _errorMessage; } }
public string ParameterName { get { return _paramName; } }
}
.. и декларация IActionFilter.
public interface IActionFilter<TAttribute> where TAttribute : Attribute
{
void OnActionExecuting(TAttribute attr, HttpActionContext ctx);
}
Все казалось хорошо, пока я не понял, что в то время как мой ActionFilter устанавливает ActionContext.Response на «сообщения об ошибке» ...
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, "my error msg");
Это не то возвращения сказал BadRequest обратно к абоненту и вместо Я заканчиваю код моего действия, как будто фильтр даже не был выполнен.
Вот суть моего кода ActionFilter/'behavior.
public class CheckStringParamLengthActionFilter : IActionFilter<CheckStringParamLengthAttribute>
{
...
public void OnActionExecuting(
CheckStringParamLengthAttribute attribute,
HttpActionContext actionContext)
{
Debug.WriteLine("OnActionExecuting (Parameter Name being checked: " + attribute.ParameterName + ")");
// get the attribute from the method specified in the ActionContext, if there.
var attr = this.GetStringLengthAttribute(
actionContext.ActionDescriptor);
if (actionContext.ActionArguments.Count < 1) {
throw new Exception("Invalid number of ActionArguments detected.");
}
var kvp = actionContext.ActionArguments
.Where(k => k.Key.Equals(attr.ParameterName, StringComparison.InvariantCulture))
.First();
var paramName = kvp.Key;
var stringToCheck = kvp.Value as string;
string errorMsg;
if (stringToCheck.Length < attr.MinLength) {
errorMsg = string.IsNullOrEmpty(attr.ErrorMessage)
? string.Format(
"The {0} parameter must be at least {1} characters in length.",
paramName, attr.MinLength)
: attr.ErrorMessage;
// SEE HERE
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, errorMsg);
actionContext.Response.ReasonPhrase += " (" + errorMsg + ")";
return;
}
...
}
...
}
Вот метод Application_Start() (от Global.asax.cs), показывающий простой Injector регистрационный код и т.д.
protected void Application_Start()
{
// DI container spin up. (Simple Injector)
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();
container.Register<ILogger, Logger>(Lifestyle.Scoped);
container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
GlobalConfiguration.Configuration.Filters.Add(
new ActionFilterDispatcher(container.GetAllInstances));
container.RegisterCollection(typeof(IActionFilter<>), typeof(IActionFilter<>).Assembly);
container.Verify();
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(container);
// the rest of this is 'normal' Web API registration stuff.
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
Я подумал, что если я просто установить actionContext.Response Ань «ErrorResponse», что «Bad Request» будет отправлен обратно вызывающему, и метод действия, на который был помещен мой атрибут, даже не будет выполнен. Разочаровательно, это не так.
Итак, вопрос в том, чего мне не хватает, чтобы получить этот Bad Request, отправленный прямо назад вызывающему, не вдаваясь в метод действия? Или, если на то пошло, это даже возможно?
Push нажимает, я всегда могу вставить дополнительный экземпляр класса «служебный уровень» в контроллер (контроллеры) и иметь код в каждом методе действий, который должен вызывать валидатор длины длины строки, но это казалось, на по крайней мере, когда я начал, быть лучшим, более чистым способом.
ОБНОВЛЕНИЕ: Ну, поразите меня! Я, очевидно, забыл о самой важной части.
Я знаю это, потому что, хорошо, см. Ответ ниже.
Между тем, здесь ActionFilterDispatcher, который зарегистрирован в методе Application_Start() в Global.asax.cs. например
protected void Application_Start()
{
...
GlobalConfiguration.Configuration.Filters.Add(
new ActionFilterDispatcher(container.GetAllInstances));
...
}
зарегистрированный ActionFilter (ей) вызывается из метода ExecuteActionFilterAsync() этого класса. Это, как это бывает, является ключевым.
public sealed class ActionFilterDispatcher : IActionFilter
{
private readonly Func<Type, IEnumerable> container;
public ActionFilterDispatcher(Func<Type, IEnumerable> container)
{
this.container = container;
}
public Task<HttpResponseMessage> ExecuteActionFilterAsync(
HttpActionContext context,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
var descriptor = context.ActionDescriptor;
var attributes = descriptor.ControllerDescriptor.GetCustomAttributes<Attribute>(true)
.Concat(descriptor.GetCustomAttributes<Attribute>(true));
foreach (var attribute in attributes)
{
Type filterType = typeof(IActionFilter<>).MakeGenericType(attribute.GetType());
IEnumerable filters = this.container.Invoke(filterType);
foreach (dynamic actionFilter in filters)
{
actionFilter.OnActionExecuting((dynamic)attribute, context);
}
}
return continuation();
}
public bool AllowMultiple { get { return true; } }
}