【问题标题】:Web Api Required ParameterWeb API 必填参数
【发布时间】:2013-10-17 05:01:19
【问题描述】:

使用 ASP.NET Web API。如果参数为空,有没有办法自动返回状态码 400?我找到了这个question,但这是一个适用于所有方法的全局解决方案,我想在每个方法每个参数的基础上执行此操作。

例如,这就是我目前正在做的事情:

public HttpResponseMessage SomeMethod(SomeNullableParameter parameter)
{
    if (parameter == null)
        throw new HttpResponseException(HttpStatusCode.BadRequest);

    // Otherwise do more stuff.
}

我真的很想做这样的事情(注意 required 属性):

public HttpResponseMessage SomeMethod([Required] SomeNullableParameter parameter)
{
    // Do stuff.
}

【问题讨论】:

  • 过滤器可以接受吗?
  • 是的,我认为任何声明性解决方案都可以。
  • 从 Asp.Net Core 2.1 有一个内置验证。请参阅我的回复 stackoverflow.com/a/54533218/245460

标签: c# asp.net asp.net-web-api


【解决方案1】:

我最终使用的方法是创建一个我在全球注册的自定义过滤器。过滤器检查RequiredAttribute 的所有请求参数。如果找到该属性,则检查参数是否随请求一起传递(非空),如果为空,则返回状态代码 400。我还在过滤器中添加了一个缓存来存储每个请求所需的参数,以避免对未来调用的反射命中。我惊喜地发现这也适用于值类型,因为操作上下文将参数存储为对象。

编辑 - 根据 tecfield 的评论更新解决方案

public class RequiredParametersFilter : ActionFilterAttribute
{
    // Cache used to store the required parameters for each request based on the
    // request's http method and local path.
    private readonly ConcurrentDictionary<Tuple<HttpMethod, string>, List<string>> _Cache =
        new ConcurrentDictionary<Tuple<HttpMethod, string>, List<string>>();

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        // Get the request's required parameters.
        List<string> requiredParameters = this.GetRequiredParameters(actionContext);     

        // If the required parameters are valid then continue with the request.
        // Otherwise, return status code 400.
        if(this.ValidateParameters(actionContext, requiredParameters))
        {
            base.OnActionExecuting(actionContext);
        }
        else
        {
            throw new HttpResponseException(HttpStatusCode.BadRequest);
        }
    }

    private bool ValidateParameters(HttpActionContext actionContext, List<string> requiredParameters)
    {
        // If the list of required parameters is null or containst no parameters 
        // then there is nothing to validate.  
        // Return true.
        if (requiredParameters == null || requiredParameters.Count == 0)
        {
            return true;
        }

        // Attempt to find at least one required parameter that is null.
        bool hasNullParameter = 
            actionContext
            .ActionArguments
            .Any(a => requiredParameters.Contains(a.Key) && a.Value == null);

        // If a null required paramter was found then return false.  
        // Otherwise, return true.
        return !hasNullParameter;
    }

    private List<string> GetRequiredParameters(HttpActionContext actionContext)
    {
        // Instantiate a list of strings to store the required parameters.
        List<string> result = null;

        // Instantiate a tuple using the request's http method and the local path.
        // This will be used to add/lookup the required parameters in the cache.
        Tuple<HttpMethod, string> request =
            new Tuple<HttpMethod, string>(
                actionContext.Request.Method,
                actionContext.Request.RequestUri.LocalPath);

        // Attempt to find the required parameters in the cache.
        if (!this._Cache.TryGetValue(request, out result))
        {
            // If the required parameters were not found in the cache then get all
            // parameters decorated with the 'RequiredAttribute' from the action context.
            result = 
                actionContext
                .ActionDescriptor
                .GetParameters()
                .Where(p => p.GetCustomAttributes<RequiredAttribute>().Any())
                .Select(p => p.ParameterName)
                .ToList();

            // Add the required parameters to the cache.
            this._Cache.TryAdd(request, result);
        }

        // Return the required parameters.
        return result;
    }

}

【讨论】:

  • 小心你的缓存。您可能想使用线程安全的ConcurrentDictionary 而不是普通的Dictionary,后者不是线程安全的!
  • 这适用于嵌套字段/POST 模型吗? IE。其中参数是某种类型的类,其字段为[Required]
【解决方案2】:

在模型中的属性上设置[Required],然后检查ModelState 以查看它是否为IsValid

这将允许同时测试所有必需的属性。

请参阅@Model validation in WebAPI“未发布”部分

【讨论】:

  • 我对这种方法感到担忧,因为我可能想要处理与空参数不同的无效模型。我确实试了一下,看看它是否会起作用,但它没有。因为该对象为 null,所以从未将其添加到模型中,因此从未进行过验证。
  • 您是否在模型中将可选参数类型声明为可为空? [必需] 在不可为空的原语上返回默认值。此外,参数的顺序很重要。所有必需的参数必须在可选参数之前。只是好奇,因为这对我有用。当然,如果您想区分无效模型和空参数,这一切都无关紧要。无论如何,您仍然必须在某个时候检查 null。
  • 我确实将可选类型声明为可为空的。我在可选参数之前没有必需的参数,所以这一定是问题所在。
  • 从 Asp.Net Core 2.1 有一个内置验证。请参阅我的回复 stackoverflow.com/a/54533218/245460
  • @codeMonkey:我之前发布过,但它被删除为重复。 SO是个奇怪的地方
【解决方案3】:

asp.net core的解决方案...

[AttributeUsage(AttributeTargets.Method)]
public sealed class CheckRequiredModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var requiredParameters = context.ActionDescriptor.Parameters.Where(
            p => ((ControllerParameterDescriptor)p).ParameterInfo.GetCustomAttribute<RequiredModelAttribute>() != null).Select(p => p.Name);

        foreach (var argument in context.ActionArguments.Where(a => requiredParameters.Contains(a.Key, StringComparer.Ordinal)))
        {
            if (argument.Value == null)
            {
                context.ModelState.AddModelError(argument.Key, $"The argument '{argument.Key}' cannot be null.");
            }
        }

        if (!context.ModelState.IsValid)
        {
            var errors = context.ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
            context.Result = new BadRequestObjectResult(errors);
            return;
        }

        base.OnActionExecuting(context);
    }
}

[AttributeUsage(AttributeTargets.Parameter)]
public sealed class RequiredModelAttribute : Attribute
{
}

services.AddMvc(options =>
{
    options.Filters.Add(typeof(CheckRequiredModelAttribute));
});

public async Task<IActionResult> CreateAsync([FromBody][RequiredModel]RequestModel request, CancellationToken cancellationToken)
{
    //...
}

【讨论】:

【解决方案4】:

我们可以使用BindRequired,它来自Microsoft.AspNetCore.Mvc.ModelBinding命名空间。

public async Task<ActionResult<IEnumerable<Numbers>>> GetAll([BindRequired, FromQuery]string[] numbers)
        {
            var result = await _service.GetAllDetails(numbers);
            return Ok(result);
        }

之后,你的招摇将如下所示。

【讨论】:

    【解决方案5】:

    公认的解决方案会自行报告任何错误。 MVC5 更合适的方法是让控制器(通过模型验证)处理任何错误的报告,也就是这样:

    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Web.Http.Controllers;
    using System.Web.Http.Filters;
    using System.Web.Http.ModelBinding;
    
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
    public sealed class ValidateParametersAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext context)
        {
            var descriptor = context.ActionDescriptor;
            if (descriptor != null)
            {
                var modelState = context.ModelState;
                foreach (var parameterDescriptor in descriptor.GetParameters())
                {
                    EvaluateValidationAttributes(
                        suppliedValue: context.ActionArguments[parameterDescriptor.ParameterName],
                        modelState: modelState,
                        parameterDescriptor: parameterDescriptor
                    );
                }
            }
    
            base.OnActionExecuting(context);
        }
    
        static private void EvaluateValidationAttributes(HttpParameterDescriptor parameterDescriptor, object suppliedValue, ModelStateDictionary modelState)
        {
            var parameterName = parameterDescriptor.ParameterName;
    
            parameterDescriptor
                .GetCustomAttributes<object>()
                .OfType<ValidationAttribute>()
                .Where(x => !x.IsValid(suppliedValue))
                .ForEach(x => modelState.AddModelError(parameterName, x.FormatErrorMessage(parameterName)));
        }
    }
    

    然后您可以通过 WebApiConfig.cs 将其通用插入:

    config.Filters.Add(new ValidateParametersAttribute());
    

    【讨论】:

      猜你喜欢
      • 2016-07-18
      • 2012-09-05
      • 1970-01-01
      • 2013-02-10
      • 1970-01-01
      • 1970-01-01
      • 2020-12-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多