Behandeln Sie die ModelState-Validierung in der ASP.NET-Web-API


105

Ich habe mich gefragt, wie ich mit der ASP.NET-Web-API eine Modellvalidierung erreichen kann. Ich habe mein Modell so:

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

Ich habe dann eine Post-Aktion in meinem API-Controller:

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

Wie füge ich if(ModelState.IsValid)die Fehlermeldung hinzu und behandle sie dann, um sie an den Benutzer weiterzuleiten?

Antworten:


184

Zur Trennung von Bedenken würde ich vorschlagen, dass Sie einen Aktionsfilter für die Modellvalidierung verwenden, sodass Sie sich nicht darum kümmern müssen, wie die Validierung in Ihrem API-Controller durchgeführt wird:

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

27
Die Namensräume für diese benötigt werden System.Net.Http, System.Net System.Web.Http.Controllersund System.Web.Http.Filters.
Christopher Stevenson

11
Es gibt auch eine ähnliche Implementierung auf der offiziellen ASP.NET-Web- API-
Erik Schierboom

1
Auch wenn [ValidationActionFilter] nicht über der Web-API steht, ruft es den Code auf und gibt mir eine schlechte Anfrage.
Mikronyken

1
Es sei darauf hingewiesen, dass die zurückgegebene Fehlerantwort von IncludeErrorDetailPolicy gesteuert wird . Standardmäßig enthält die Antwort auf eine Remoteanforderung nur die allgemeine Meldung "Ein Fehler ist aufgetreten". Wenn Sie diese Einstellung jedoch so einstellen, IncludeErrorDetailPolicy.Alwayswerden die Details angezeigt (auf die Gefahr, dass Ihre Benutzer Details erfahren)
Rob

30

Vielleicht nicht das, wonach Sie gesucht haben, aber vielleicht schön, wenn jemand weiß:

Wenn Sie .net Web Api 2 verwenden, können Sie Folgendes tun:

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

Abhängig von den Modellfehlern erhalten Sie folgendes Ergebnis:

{
   Message: "The request is invalid."
   ModelState: {
       model.PropertyA: [
            "The PropertyA field is required."
       ],
       model.PropertyB: [
             "The PropertyB field is required."
       ]
   }
}

1
Denken Sie daran, als ich diese Frage stellte, wurde Web API 1 gerade veröffentlicht, es ist wahrscheinlich seitdem viel weitergegangen :)
CallumVass

Stellen Sie sicher, dass Sie die Eigenschaften als optional markieren, da sonst ein nicht hilfreiches generisches "Ein Fehler ist aufgetreten" angezeigt wird. Fehlermeldung.
Bouke

1
Gibt es eine Möglichkeit, die Nachricht zu ändern?
Saquib Adil

28

So zum Beispiel:

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

Dies gibt eine Antwort wie diese zurück (unter der Annahme von JSON, aber dem gleichen Grundprinzip für 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."]

Sie können Ihr Fehlerobjekt / Ihre Fehlerliste natürlich beliebig erstellen, z. B. Feldnamen, Feld-IDs usw. hinzufügen.

Auch wenn es sich um einen Einweg-Ajax-Aufruf wie einen POST einer neuen Entität handelt, sollten Sie dem Anrufer dennoch etwas zurückgeben - etwas, das angibt, ob die Anforderung erfolgreich war oder nicht. Stellen Sie sich eine Site vor, auf der Ihr Benutzer über eine AJAX POST-Anfrage einige Informationen über sich selbst hinzufügt. Was passiert, wenn die Informationen, die sie eingegeben haben, nicht gültig sind? Woher wissen sie, ob ihre Aktion Speichern erfolgreich war oder nicht?

Der beste Weg, dies zu tun, ist die Verwendung von guten alten HTTP-Statuscodes wie 200 OKund so weiter. Auf diese Weise kann Ihr JavaScript Fehler mit den richtigen Rückrufen (Fehler, Erfolg usw.) ordnungsgemäß behandeln.

Hier ist ein nettes Tutorial zu einer fortgeschritteneren Version dieser Methode mit ActionFilter und jQuery: http://asp.net/web-api/videos/getting-started/custom-validation


Das gibt nur mein enquiryObjekt zurück, es sagt aber nicht, welche Eigenschaften ungültig sind? Wenn ich also CustomerAccountNumberleer gelassen habe , sollte die Standardüberprüfungsnachricht angezeigt werden (CusomterAccountNumber-Feld ist erforderlich.)
CallumVass

Ich verstehe, ist dies dann die "richtige" Art, mit der Modellvalidierung umzugehen? Scheint mir ein bisschen chaotisch ..
CallumVass

Es gibt auch andere Möglichkeiten, dies zu tun, z. B. die Verbindung mit der jQuery-Validierung herzustellen. Hier ist ein schönes Microsoft-Beispiel: asp.net/web-api/videos/getting-started/custom-validation
Anders Arpi

Diese Methode und die als Antwort gewählte Methode "sollten" funktional identisch sein, daher hat diese Antwort den Mehrwert, Ihnen zu zeigen, wie Sie es selbst ohne einen Aktionsfilter tun könnten.
Shaun Wilson

Ich hatte die Linie zu ändern , errors.Add(error.ErrorMessage);um errors.Add(error.Exception.Message);diese Arbeit für mich zu bekommen.
Caltor

9

8

Oder wenn Sie nach einer einfachen Sammlung von Fehlern für Ihre Apps suchen, ist hier meine Implementierung:

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

Die Antwort auf die Fehlermeldung sieht folgendermaßen aus:

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

5

Fügen Sie den folgenden Code in die Datei startup.cs ein

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).ConfigureApiBehaviorOptions(options =>
            {
                options.InvalidModelStateResponseFactory = (context) =>
                {
                    var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p => new ErrorModel()
                   {
                       ErrorCode = ((int)HttpStatusCode.BadRequest).ToString(CultureInfo.CurrentCulture),
                        ErrorMessage = p.ErrorMessage,
                        ServerErrorMessage = string.Empty
                    })).ToList();
                    var result = new BaseResponse
                    {
                        Error = errors,
                        ResponseCode = (int)HttpStatusCode.BadRequest,
                        ResponseMessage = ResponseMessageConstants.VALIDATIONFAIL,

                    };
                    return new BadRequestObjectResult(result);
                };
           });

3

Hier können Sie überprüfen, ob der Modellstatusfehler einzeln angezeigt wird

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

}}


3

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

Ich hatte ein Problem bei der Implementierung des akzeptierten Lösungsmusters, bei dem ich für bestimmte ModelStateFilterModellobjekte immer false(und anschließend 400) actionContext.ModelState.IsValidzurückgab:

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

Ich akzeptiere nur JSON, daher habe ich eine benutzerdefinierte Modellordnerklasse implementiert:

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

Was ich direkt nach meinem Modell über registriere

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.