2013-10-09 7 views
30

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

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

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. 
} 
+0

Будет ли фильтр приемлемым? –

+0

Да, я думаю, что любое декларативное решение будет в порядке. –

ответ

15

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

EDIT - Обновлены решение, основанное на комментарий 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; 
    } 

} 
+6

Будьте осторожны с кешем. вы можете использовать поточный «ConcurrentDictionary», а не обычный «Словарь», который не является потокобезопасным! – tecfield

+0

Это работает для вложенных полей/'POST' моделей? То естьгде параметр является классом какого-либо типа, который имеет поля '[Обязательный]'. – Zero3

4

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

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

Смотрите раздел «Под-проводки» @Model validation in WebAPI

+1

У меня были проблемы с этим подходом, потому что я могу обработать недопустимую модель, отличную от нулевого параметра. Я попробовал хотя бы посмотреть, будет ли это работать, а это не так. Поскольку объект был нулевым, он никогда не добавлялся в модель, поэтому проверка не происходила. –

+0

Вы объявили необязательный тип параметра как nullable в своей модели? [Требуется] для примитивов с непустым значением возвращает значение по умолчанию. Также важно упорядочить параметры. Все обязательные параметры должны предшествовать дополнительным параметрам. Просто любопытно, так как это работает для меня. Конечно, это не имеет значения, если вы хотите различать недопустимую модель и нулевые параметры. В любом случае вам все равно нужно проверить значение null. –

+0

Я объявлял необязательный тип нулевым. У меня не было требуемого параметра перед дополнительными параметрами, поэтому это должно было быть проблемой. –

0

Раствор для ядра 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) 
{ 
    //... 
} 
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()); 
Смежные вопросы