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?

CallumVass
quelle

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);
        }
    }
}
cuongle
quelle
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."
       ]
   }
}
Sind Almaas
quelle
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

Anders Arpi
quelle
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

Sie können Attribute aus dem System.ComponentModel.DataAnnotationsNamespace verwenden, um Validierungsregeln festzulegen. Weitere Informationen finden Sie unter Modellvalidierung - Von Mike Wasson .

Siehe auch Video ASP.NET Web API, Teil 5: Benutzerdefinierte Validierung - Jon Galloway

Andere Referenzen

  1. Machen Sie mit WebAPI und WebForms einen Spaziergang auf der Clientseite
  2. Wie die ASP.NET-Web-API HTTP-Nachrichten an Domänenmodelle bindet und wie mit Medienformaten in der Web-API gearbeitet wird.
  3. Dominick Baier - Sichern von ASP.NET-Web-APIs
  4. Verknüpfen der AngularJS-Validierung mit der ASP.NET-Web-API-Validierung
  5. Anzeigen von ModelState-Fehlern mit AngularJS in ASP.NET MVC
  6. Wie rendere ich Fehler auf dem Client? AngularJS / WebApi ModelState
  7. Abhängigkeitsinjizierte Validierung in der Web-API
LCJ
quelle
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"
  ]
}
Sandeep Talabathula
quelle
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);
                };
           });
MayankGaur
quelle
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
        }
        }

}}

Debendra Dash
quelle
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);
};
Nick Hermans
quelle
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());
user326608
quelle