Web Api 必需参数

40

使用ASP.NET Web API。是否有一种方法可以在参数为空时自动返回状态码400?我发现了这个问题,但那是一个应用于所有方法的全局解决方案,我想按每个方法每个参数来做。

所以,例如,这就是我目前正在做的:

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

    // Otherwise do more stuff.
}

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

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

一个过滤器是否可行? - Mister Epic
是的,我认为任何声明式解决方案都可以。 - Jason Boyd
从Asp.Net Core 2.1开始,已经内置了验证功能。请查看我的回答stackoverflow.com/a/54533218/245460。 - Karel Kral
5个回答

25

我的解决方案是创建一个自定义过滤器并将其注册为全局过滤器。该过滤器将检查所有请求参数是否包含RequiredAttribute。如果找到这个属性,则检查请求中是否传递了该参数(非空),如果为null,则返回状态码400。我还向过滤器添加了缓存,以存储每个请求所需的参数,以避免反射对未来调用的影响。令人惊喜的是,它也适用于值类型,因为ActionContext将参数存储为对象。

编辑 - 根据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;
    }

}

7
请注意缓存。您可能希望使用线程安全的 ConcurrentDictionary 而不是不安全的普通 Dictionary - tecfield
这对嵌套字段/POST模型有效吗?即参数是某种类,该类具有被标记为[Required]的字段。 - Zero3

6
在您的模型中设置 [Required] 属性,然后检查 ModelState 是否为 IsValid
这将同时测试所有必需的属性。
请参阅“下面发布”部分 @ Model validation in WebAPI

2
我对这种方法有所顾虑,因为我可能想要处理无效模型和空参数的方式不同。尽管如此,我还是试了一下,但并没有成功。由于对象为空,它从未被添加到模型中,因此验证也从未发生。 - Jason Boyd
我已将可选类型声明为可空。我没有在可选参数之前放置必需的参数,这可能是问题所在。 - Jason Boyd
你的解决方案看起来是一个很好的通用方法,可以提供400服务。 - Timothy Lee Russell
3
从Asp.Net Core 2.1开始,有内置的验证功能。请查看我的回答 stackoverflow.com/a/54533218/245460。 - Karel Kral
1
@codeMonkey:我之前发布过,但被删除了,因为重复了。SO是个奇怪的地方。 - Karel Kral
显示剩余2条评论

3

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

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

然后你的Swagger看起来会像下面这样。

输入图片描述


2
一种适用于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)
{
    //...
}

从Asp.Net Core 2.1开始,有内置的验证功能。请查看我的回答https://dev59.com/31QJ5IYBdhLWcg3w2Jm8#54533218 - Karel Kral

0

被接受的解决方案会自行报告任何错误。对于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());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接