2013-10-09 2 views
30

Utilisation de l'API Web ASP.NET. Est-il possible de renvoyer automatiquement un code d'état 400 si un paramètre est null? J'ai trouvé ceci question mais c'est une solution globale qui est appliquée à toutes les méthodes, je veux le faire sur la base d'une méthode par paramètre.Web API requis Paramètre

Ainsi, par exemple, ce que je fais actuellement:

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

    // Otherwise do more stuff. 
} 

Je vraiment juste envie de faire quelque chose comme ceci (notez l'attribut obligatoire):

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

Un filtre serait-il acceptable? –

+0

Oui, je pense que n'importe quelle solution déclarative irait bien. –

Répondre

15

L'approche que je fini par utiliser était de créer un filtre personnalisé que je me suis inscrit à l'échelle mondiale. Le filtre vérifie tous les paramètres de requête pour 'RequiredAttribute'. Si l'attribut est trouvé, il vérifie si le paramètre a été passé avec la requête (not null) et renvoie le code d'état 400 s'il était nul. J'ai également ajouté un cache au filtre pour stocker les paramètres requis pour chaque requête afin d'éviter la réflexion sur les futurs appels. J'ai été agréablement surpris de constater que cela fonctionne également pour les types de valeur puisque le contexte d'action stocke les paramètres en tant qu'objets.

EDIT - solution Mise à jour basée sur le commentaire de 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

Faites attention à votre cache. vous voudrez peut-être utiliser un 'ConcurrentDictionary' sécurisé au lieu d'un' Dictionary' normal qui n'est pas thread-safe! – tecfield

+0

Est-ce que cela fonctionne pour les champs imbriqués/modèles 'POST'? C'est à dire.où le paramètre est une classe quelconque dont les champs sont '[Obligatoire]'. – Zero3

4

Set [ Obligatoire] sur une propriété dans votre modèle, puis vérifiez le ModelState pour voir s'il est IsValid.

Ceci permettra de tester toutes les propriétés requises en même temps.

Voir la « sous-affichage » @ section Model validation in WebAPI

+1

J'ai eu des soucis avec cette approche parce que je peux vouloir manipuler un modèle invalide différent d'un paramètre nul. J'ai essayé cependant de voir si cela fonctionnerait et ce n'était pas le cas. Comme l'objet était nul, il n'a jamais été ajouté au modèle, donc la validation n'a jamais eu lieu. –

+0

Avez-vous déclaré le type de paramètre facultatif comme pouvant être validé dans votre modèle? [Obligatoire] sur les primitives non nullables renvoie la valeur par défaut. En outre, l'ordre des paramètres est important. Tous les paramètres requis doivent précéder les paramètres facultatifs. Juste curieux, puisque cela fonctionne pour moi. Bien sûr, cela n'est pas pertinent si vous voulez faire la différence entre un modèle invalide et un paramètre nul. Vous devez toujours vérifier null à un moment donné de toute façon. –

+0

J'ai déclaré le type facultatif comme nullable. Je n'avais pas le paramètre requis avant les paramètres optionnels, cela a dû être le problème. –

0

Une solution pour noyau 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

La solution retenue se prend sur lui faire rapport des erreurs . Une approche plus appropriée pour MVC5 est de laisser la poignée de commande (via la validation du modèle), la déclaration des erreurs, alias quelque chose comme ceci:

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))); 
    } 
} 

Vous pouvez alors le brancher universellement via WebApiConfig.cs:

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