web-dev-qa-db-fra.com

Gestion de la validation ModelState dans l'API Web ASP.NET

Je me demandais comment obtenir une validation de modèle avec l’API Web ASP.NET. J'ai mon modèle comme suit:

public class Enquiry
{
    [Key]
    public int EnquiryId { get; set; }
    [Required]
    public DateTime EnquiryDate { get; set; }
    [Required]
    public string CustomerAccountNumber { get; set; }
    [Required]
    public string ContactName { get; set; }
}

J'ai ensuite une action Post dans mon contrôleur API:

public void Post(Enquiry enquiry)
{
    enquiry.EnquiryDate = DateTime.Now;
    context.DaybookEnquiries.Add(enquiry);
    context.SaveChanges();
}

Comment puis-je ajouter if(ModelState.IsValid) puis gérer le message d'erreur à transmettre à l'utilisateur?

97
CallumVass

Pour séparer les préoccupations, nous vous suggérons d'utiliser un filtre d'action pour la validation du modèle. Vous n'avez donc pas à vous soucier beaucoup de la validation dans votre contrôleur d'api:

using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace System.Web.Http.Filters
{
    public class ValidationActionFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var modelState = actionContext.ModelState;

            if (!modelState.IsValid)
                actionContext.Response = actionContext.Request
                     .CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
        }
    }
}
176
cuongle

Comme ceci, par exemple:

public HttpResponseMessage Post(Person person)
{
    if (ModelState.IsValid)
    {
        PersonDB.Add(person);
        return Request.CreateResponse(HttpStatusCode.Created, person);
    }
    else
    {
        // the code below should probably be refactored into a GetModelErrors
        // method on your BaseApiController or something like that

        var errors = new List<string>();
        foreach (var state in ModelState)
        {
            foreach (var error in state.Value.Errors)
            {
                errors.Add(error.ErrorMessage);
            }
        }
        return Request.CreateResponse(HttpStatusCode.Forbidden, errors);
    }
}

Cela retournera une réponse comme ceci (en supposant que JSON, mais le même principe de base pour XML):

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
(some headers removed here)

["A value is required.","The field First is required.","Some custom errorm essage."]

Vous pouvez bien sûr construire votre objet/liste d'erreur comme bon vous semble, par exemple en ajoutant des noms de champs, des identifiants de champs, etc.

Même s'il s'agit d'un appel Ajax "à sens unique" semblable à un POST d'une nouvelle entité, vous devez toujours renvoyer quelque chose à l'appelant - quelque chose qui indique si la demande a abouti ou non. Imaginez un site où votre utilisateur va ajouter des informations sur lui-même via une demande AJAX POST. Demande. Si les informations qu'il a essayé d'entrer ne sont pas valides - comment vont-ils savoir si leur action Save a réussi ou non?

La meilleure façon de faire est d’utiliser de bons vieux codes d’état HTTP comme 200 OK etc. De cette façon, votre JavaScript peut gérer correctement les échecs en utilisant les rappels appropriés (erreur, succès, etc.).

Voici un bon tutoriel sur une version plus avancée de cette méthode, utilisant un ActionFilter et jQuery: http://asp.net/web-api/videos/getting-started/custom-validation

28
Anders Arpi

Peut-être pas ce que vous cherchiez, mais peut-être agréable pour quelqu'un de savoir:

Si vous utilisez .net Web Api 2, vous pouvez simplement procéder comme suit:

if (!ModelState.IsValid)
     return BadRequest(ModelState);

En fonction des erreurs de modèle, vous obtenez le résultat suivant:

{
   Message: "The request is invalid."
   ModelState: {
       model.PropertyA: [
            "The PropertyA field is required."
       ],
       model.PropertyB: [
             "The PropertyB field is required."
       ]
   }
}
24
Are Almaas
9
Lijo

Ou, si vous recherchez une simple collection d'erreurs pour vos applications .. voici ma mise en œuvre:

public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var modelState = actionContext.ModelState;

        if (!modelState.IsValid) 
        {

            var errors = new List<string>();
            foreach (var state in modelState)
            {
                foreach (var error in state.Value.Errors)
                {
                    errors.Add(error.ErrorMessage);
                }
            }

            var response = new { errors = errors };

            actionContext.Response = actionContext.Request
                .CreateResponse(HttpStatusCode.BadRequest, response, JsonMediaTypeFormatter.DefaultMediaType);
        }
    }

La réponse du message d'erreur ressemblera à ceci:

{
  "errors": [
    "Please enter a valid phone number (7+ more digits)",
    "Please enter a valid e-mail address"
  ]
}
7
sandeep talabathula

C #

    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ModelState.IsValid == false)
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }

...

    [ValidateModel]
    public HttpResponseMessage Post([FromBody]AnyModel model)
    {

Javascript

$.ajax({
        type: "POST",
        url: "/api/xxxxx",
        async: 'false',
        contentType: "application/json; charset=utf-8",
        data: JSON.stringify(data),
        error: function (xhr, status, err) {
            if (xhr.status == 400) {
                DisplayModelStateErrors(xhr.responseJSON.ModelState);
            }
        },
....


function DisplayModelStateErrors(modelState) {
    var message = "";
    var propStrings = Object.keys(modelState);

    $.each(propStrings, function (i, propString) {
        var propErrors = modelState[propString];
        $.each(propErrors, function (j, propError) {
            message += propError;
        });
        message += "\n";
    });

    alert(message);
};
2
Nick Hermans

Ici, vous pouvez vérifier que l’erreur d’état du modèle est affichée une par une

 public HttpResponseMessage CertificateUpload(employeeModel emp)
    {
        if (!ModelState.IsValid)
        {
            string errordetails = "";
            var errors = new List<string>();
            foreach (var state in ModelState)
            {
                foreach (var error in state.Value.Errors)
                {
                    string p = error.ErrorMessage;
                    errordetails = errordetails + error.ErrorMessage;

                }
            }
            Dictionary<string, object> dict = new Dictionary<string, object>();



            dict.Add("error", errordetails);
            return Request.CreateResponse(HttpStatusCode.BadRequest, dict);


        }
        else
        {
      //do something
        }
        }

}

2
Debendra Dash

J'ai eu un problème lors de la mise en œuvre du modèle de solution accepté où mon ModelStateFilter retournait toujours false (puis un 400) pour actionContext.ModelState.IsValid pour certains objets du modèle:

public class ModelStateFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest};
        }
    }
}

J'accepte uniquement le format JSON. J'ai donc implémenté une classe de classeur de modèles personnalisée:

public class AddressModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
    {
        var posted = actionContext.Request.Content.ReadAsStringAsync().Result;
        AddressDTO address = JsonConvert.DeserializeObject<AddressDTO>(posted);
        if (address != null)
        {
            // moar val here
            bindingContext.Model = address;
            return true;
        }
        return false;
    }
}

Que j’enregistre directement après mon modèle via

config.BindParameter(typeof(AddressDTO), new AddressModelBinder());
1
user326608

Vous pouvez également lancer des exceptions comme indiqué ici: http://blogs.msdn.com/b/youssefm/archive/2012/06/28/error-handling-in-asp-net-webapi.aspx =

Remarque: pour faire ce que cet article suggère, n'oubliez pas d'inclure System.Net.Http

1
Christopher Davies