MVC 控制器在验证查询字符串参数时应该 return 400

MVC controller should return 400 when validation query string parameters

假设您在 MVC 控制器中有一个简单的方法...

[Route("{id}", Name = "GetById")]
[HttpGet]
[ProducesResponseType(typeof(SomeType), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.Unauthorized)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.Forbidden)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.ServiceUnavailable)]
public async Task<IActionResult> Get(string id, bool? includeNonActive = false)
{
    // some stuff
}

如果向 includeNonActive 查询字符串参数传递了无效值(非布尔值),是否有任何方法可以让 MVC 自动 return HTTP 400?

HTTP GET http://my-server/api/12321312?includeNonActive=thisIsNotABooleanValue

当然,我可以接受 'includeNonActive' 的字符串类型,使用 Boolean.TryParse()return BadRequest("wtf"),但这在 Swagger 中看起来很难看。

Is there any way to get MVC to automagically return a HTTP 400 if invalid value (non-boolean) are passed to the 'includeNonActive' query string parameter?

当然,您可以为此构建一个操作过滤器。

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class EnsureBooleanQueryParameterAttribute : ActionFilterAttribute
{
    public EnsureBooleanQueryParameterAttribute(string parameterName)
    {
        if (string.IsNullOrEmpty(parameterName))
            throw new ArgumentException($"'{nameof(parameterName)}' is required.");
        this.ParameterName = parameterName;
    }

    public string ParameterName { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var attribute = (EnsureBooleanQueryParameterAttribute)filterContext.ActionDescriptor
            .GetCustomAttributes(typeof(EnsureBooleanQueryParameterAttribute), true)
            .FirstOrDefault();

        // The attribute exists on the current action method
        if (attribute != null)
        {
            string param = filterContext.HttpContext.Request.QueryString[attribute.ParameterName];
            bool result;
            // If the query string value is present and not boolean
            if (!string.IsNullOrEmpty(param) && !bool.TryParse(param, out result))
            {
                filterContext.Result = new HttpStatusCodeResult(400, 
                    "Invalid boolean query string value for '{attribute.ParameterName}'.");
            }
        }
    }
}

然后像这样使用过滤器:

[Route("{id}", Name = "GetById")]
[HttpGet]
[ProducesResponseType(typeof(SomeType), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.Unauthorized)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.Forbidden)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.ServiceUnavailable)]
[EnsureBooleanQueryParameter("includeNonActive")]
public async Task<IActionResult> Get(string id, bool? includeNonActive = false)
{
    // some stuff
}

您可以以此为起点,添加可用于指定数据类型的 Enum 参数和用于指示值是否必须存在的 bool 参数在 URL.

我没有指定我使用的是 MVC Core,因此我必须更改一些内容才能让它为我工作。这是我的版本。

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class BooleanQueryStringParameterTypeValidatorAttribute : ActionFilterAttribute
{
    public BooleanQueryStringParameterTypeValidatorAttribute(string parameterName)
    {
        if(string.IsNullOrWhiteSpace(parameterName))
            throw new ArgumentException($"'{nameof(parameterName)}' is required.");
        this.ParameterName = parameterName;
    }

    protected string ParameterName { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if(!filterContext.HttpContext.Request.QueryString.HasValue)
            return;

        ControllerActionDescriptor actionDescriptor = filterContext.ActionDescriptor as ControllerActionDescriptor;
        var attribute = actionDescriptor.MethodInfo.GetCustomAttributes(typeof(BooleanQueryStringParameterTypeValidatorAttribute), true).FirstOrDefault() as BooleanQueryStringParameterTypeValidatorAttribute;

        // The attribute exists on the current action method
        if(attribute != null)
        {
            Dictionary<string, StringValues> queryString = QueryHelpers.ParseNullableQuery(filterContext.HttpContext.Request.QueryString.Value);
            if(queryString != null && queryString.ContainsKey(attribute.ParameterName))
            {
                bool result;
                // If the query string value is present and not boolean
                if(!string.IsNullOrEmpty(queryString[attribute.ParameterName]) && !bool.TryParse(queryString[attribute.ParameterName], out result))
                {
                    filterContext.Result = new BadRequestObjectResult($"Invalid boolean query string value for '{attribute.ParameterName}'.");
                }
            }
        }
    }
}