0

Я написал 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; } } 
} 

ответ

0

Для того, чтобы дать кредит, где кредит должен, большой и очень полезный разработчик [от # asp.net канала на EFNet] дал мне ответ на этот вопрос.

Поскольку ActionFilter вызывается из метода ExecuteActionFilterAsync() этого класса, мне нужно было добавить очень простой оператор if, чтобы проверить, был ли заполнен объект HttpActionContext.Response, и если да, немедленно выйдите, который затем отправляет созданный ответ обратно вызывающему абоненту.

Вот фиксированный метод.

public sealed class ActionFilterDispatcher : IActionFilter 
{ 
    ... 

    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); 

       // ADDED THIS in order to send my BadRequest response right 
       // back to the caller [of the Web API endpoint] 
       if (context.Response != null) 
       { 
        return Task.FromResult(context.Response); 
       } 
      } 
     } 

     return continuation(); 
    } 
    ... 
} 
Смежные вопросы