首页 文章

为什么不在OnActionExecuting()方法中将ActionContext.Response设置为BadRequest直接将其返回给调用者?

提问于
浏览
0

我编写了一个ActionFilter来检查传递给任何给定[Web API]操作方法的指定字符串参数的长度,如果长度不正确,则将ActionContext.Response设置为HttpStatusCode.BadRequest(通过调用actionContext.Request.CreateErrorResponse()),但我仍然在我的动作方法代码中结束 . 这基本上意味着像人们创建的所有ActionFilterAttribute类一样工作,以处理动作方法之外的ModelState的验证,但我也需要依赖注入,以便我可以使用 Logger ,并拥有我的Attribute / ActionFilter是可以测试的 .

我的搜索发现了这篇博文,其中作者描述了一种方法,即"passive Attribute"(其中Attrib基本上是DTO)和'scanning' ActionFilter,它实现了所述属性的行为 . https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=98

我遇到的问题如下;

(对不起,伙计们,请耐心等待 . 虽然我有很多年的C#经验,但这是我第一次真正进入属性和/或ActionFilter(s))

我把我的属性写成被动(其中属性只是一个DTO),以及一个继承自IActionFilter <CheckStringParamLengthAttribute>的ActionFilter,作为上述博客文章中的示例 .

这是我的属性代码 .

[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),显示了Simple 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'将被发送回调用者,并且我的属性所在的action方法甚至都不会被执行 . 令人沮丧的是,事实并非如此 .

所以问题是,为了在不进入动作方法的情况下将错误请求直接发送回调用者,我错过了什么?或者就此而言,这甚至可能吗?

Push推进了,我总是可以将另一个'服务层'类实例注入到Controller中,并且在每个需要调用/参数字符串参数长度验证器的操作方法中都有代码,但这似乎,至少在我开始时,是一个更好,更清洁的方式 .

UPDATE: 好吧,我!我显然忘记了最重要的部分 .

我知道这一点,因为,请看下面的答案 .

同时,这里是ActionFilterDispatcher,它在Global.asax.cs的Application_Start()方法中注册 . 例如

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

1 回答

  • 0

    为了给予信用到期的信誉,一位伟大且非常有帮助的开发人员[来自efnet上的#asp.net Channels ]给了我这个问题的答案 .

    由于从此类的ExecuteActionFilterAsync()方法调用ActionFilter,我需要添加一个非常简单的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();
        }
        ...
    }
    

相关问题