Modellvalidierung in der ASP.NET-Web-API

In diesem Artikel erfahren Sie, wie Sie Ihre Modelle mit Anmerkungen versehen, die Anmerkungen für die Datenüberprüfung verwenden und Validierungsfehler in Ihrer Web-API behandeln. Wenn ein Client Daten an Ihre Web-API sendet, möchten Sie die Daten häufig vor der Verarbeitung überprüfen.

Datenanmerkungen

In ASP.NET-Web-API können Sie Attribute aus dem System.ComponentModel.DataAnnotations-Namespace verwenden, um Validierungsregeln für Eigenschaften ihres Modells festzulegen. Sehen Sie sich das folgende Modell an:

using System.ComponentModel.DataAnnotations;

namespace MyApi.Models
{
    public class Product
    {
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
        public decimal Price { get; set; }
        [Range(0, 999)]
        public double Weight { get; set; }
    }
}

Wenn Sie die Modellüberprüfung in ASP.NET MVC verwendet haben, sollte dies vertraut sein. Das Erforderliche Attribut besagt, dass die Name Eigenschaft nicht NULL sein darf. Das Range-Attribut gibt an, dass Weight zwischen 0 und 999 liegen muss.

Angenommen, ein Client sendet eine POST-Anforderung mit der folgenden JSON-Darstellung:

{ "Id":4, "Price":2.99, "Weight":5 }

Sie können sehen, dass der Client die Name -Eigenschaft nicht enthält, die als erforderlich gekennzeichnet ist. Wenn die Web-API den JSON-Code in einen Product instance konvertiert, überprüft sie anhand Product der Validierungsattribute. In Ihrer Controlleraktion können Sie überprüfen, ob das Modell gültig ist:

using MyApi.Models;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace MyApi.Controllers
{
    public class ProductsController : ApiController
    {
        public HttpResponseMessage Post(Product product)
        {
            if (ModelState.IsValid)
            {
                // Do something with the product (not shown).

                return new HttpResponseMessage(HttpStatusCode.OK);
            }
            else
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
            }
        }
    }
}

Die Modellüberprüfung garantiert nicht, dass Clientdaten sicher sind. Möglicherweise ist eine zusätzliche Überprüfung in anderen Ebenen der Anwendung erforderlich. (Beispielsweise kann die Datenschicht Fremdschlüsseleinschränkungen erzwingen.) Im Tutorial Using Web API with Entity Framework (Verwenden der Web-API mit Entity Framework ) werden einige dieser Probleme erläutert.

"Under-Posting": Die Unterbuchung tritt auf, wenn der Client einige Eigenschaften auslässt. Angenommen, der Client sendet Folgendes:

{"Id":4, "Name":"Gizmo"}

Hier hat der Client keine Werte für Price oder Weightangegeben. Der JSON-Formatierer weist den fehlenden Eigenschaften den Standardwert 0 zu.

Screenshot des Codeausschnitts mit Punktmodellen im Produktspeicher, in dem die Dropdownmenüoptionen des Produkts darüber angezeigt werden.

Der Modellstatus ist gültig, da null ein gültiger Wert für diese Eigenschaften ist. Ob dies ein Problem ist, hängt von Ihrem Szenario ab. In einem Aktualisierungsvorgang können Sie beispielsweise zwischen "null" und "nicht festgelegt" unterscheiden. Wenn Clients das Festlegen eines Werts erzwingen möchten, stellen Sie die Eigenschaft NULLable fest, und legen Sie das Attribut Required fest:

[Required]
public decimal? Price { get; set; }

"Over-Posting": Ein Client kann auch mehr Daten als erwartet senden. Beispiel:

{"Id":4, "Name":"Gizmo", "Color":"Blue"}

Hier enthält JSON eine Eigenschaft ("Color"), die Product im Modell nicht vorhanden ist. In diesem Fall ignoriert der JSON-Formatierer diesen Wert einfach. (Der XML-Formatierer macht das gleiche.) Die Überbuchung verursacht Probleme, wenn Ihr Modell über Eigenschaften verfügt, die schreibgeschützt sein sollen. Beispiel:

public class UserProfile
{
    public string Name { get; set; }
    public Uri Blog { get; set; }
    public bool IsAdmin { get; set; }  // uh-oh!
}

Sie möchten nicht, dass Benutzer die IsAdmin Eigenschaft aktualisieren und sich an Administratoren erhöhen! Die sicherste Strategie besteht darin, eine Modellklasse zu verwenden, die genau dem entspricht, was der Client senden darf:

public class UserProfileDTO
{
    public string Name { get; set; }
    public Uri Blog { get; set; }
    // Leave out "IsAdmin"
}

Hinweis

Brad Wilsons Blogbeitrag "Input Validation vs. Model Validation in ASP.NET MVC" hat eine gute Diskussion über Unter- und Überposting. Obwohl es in dem Beitrag um ASP.NET MVC 2 geht, sind die Probleme weiterhin relevant für die Web-API.

Behandeln von Validierungsfehlern

Die Web-API gibt nicht automatisch einen Fehler an den Client zurück, wenn die Überprüfung fehlschlägt. Es liegt an der Controlleraktion, den Modellzustand zu überprüfen und entsprechend zu reagieren.

Sie können auch einen Aktionsfilter erstellen, um den Modellstatus zu überprüfen, bevor die Controlleraktion aufgerufen wird. Der folgende Code zeigt ein Beispiel:

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Http.ModelBinding;

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

Wenn die Modellüberprüfung fehlschlägt, gibt dieser Filter eine HTTP-Antwort zurück, die die Überprüfungsfehler enthält. In diesem Fall wird die Controlleraktion nicht aufgerufen.

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Date: Tue, 16 Jul 2013 21:02:29 GMT
Content-Length: 331

{
  "Message": "The request is invalid.",
  "ModelState": {
    "product": [
      "Required property 'Name' not found in JSON. Path '', line 1, position 17."
    ],
    "product.Name": [
      "The Name field is required."
    ],
    "product.Weight": [
      "The field Weight must be between 0 and 999."
    ]
  }
}

Um diesen Filter auf alle Web-API-Controller anzuwenden, fügen Sie der HttpConfiguration.Filters-Auflistung während der Konfiguration eine instance des Filters hinzu:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new ValidateModelAttribute());

        // ...
    }
}

Eine weitere Möglichkeit besteht darin, den Filter als Attribut für einzelne Controller oder Controlleraktionen festzulegen:

public class ProductsController : ApiController
{
    [ValidateModel]
    public HttpResponseMessage Post(Product product)
    {
        // ...
    }
}