Подтвердить что ты не робот

Web Api Обязательный параметр

Использование ASP.NET Web API. Есть ли способ автоматически возвращать код состояния 400, если параметр имеет значение null? Я нашел это question, но это глобальное решение, применяемое ко всем методам, я хочу сделать это по каждому методу на каждый параметр.

Итак, например, это то, что я сейчас делаю:

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.
}
4b9b3361

Ответ 1

Подход, который я в итоге использовал, заключался в создании настраиваемого фильтра, который я зарегистрировал глобально. Фильтр проверяет все параметры запроса для RequiredAttribute. Если атрибут найден, он проверяет, был ли передан параметр с запросом (не нулевым), и возвращает код состояния 400, если он был нулевым. Я также добавил к фильтру кэш, в котором хранятся необходимые параметры для каждого запроса, чтобы избежать попадания отражения в будущие вызовы. Я был приятно удивлен, обнаружив, что это работает и для типов значений, так как контекст действия хранит параметры как объекты.

РЕДАКТИРОВАТЬ - Обновленное решение на основе комментариев Tecfield

public class RequiredParametersFilter : ActionFilterAttribute
{
    // Cache used to store the required parameters for each request based on the
    // request 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 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 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;
    }

}

Ответ 2

Установите [Required] для свойства в вашей модели, а затем проверьте ModelState чтобы увидеть, является ли оно IsValid.

Это позволит одновременно протестировать все необходимые свойства.

См. Раздел " Недостаточная публикация " @Проверка модели в WebAPI

Ответ 3

Решение для ядра asp.net...

[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

Принятое решение берет на себя ответственность сообщать о любых ошибках. Более подходящий подход для 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());

Ответ 5

В Asp.Net Core 2.1 есть встроенный параметр [BindRequired], который выполняет эту проверку автоматически.

public async Task<ActionResult<string>> CleanStatusesAsync([BindRequired, 
    FromQuery]string collection, [BindRequired, FromQuery]string repository,
       [BindRequired, FromQuery]int pullRequestId)
{
    // all parameters are bound and valid
}

Если вы вызываете этот метод без параметров, возвращается ошибка ModelState:

{
"collection": [
  "A value for the 'collection' parameter or property was not provided."
],
"repository": [
  "A value for the 'repository' parameter or property was not provided."
],
"pullRequestId": [
  "A value for the 'pullRequestId' parameter or property was not provided."
],
}

Более подробную информацию вы можете найти в этой превосходной статье.