Walidacja modelu w internetowym interfejsie API ASP.NET

W tym artykule pokazano, jak dodawać adnotacje do modeli, używać adnotacji do walidacji danych i obsługiwać błędy walidacji w internetowym interfejsie API. Gdy klient wysyła dane do internetowego interfejsu API, często chcesz zweryfikować dane przed wykonaniem jakiegokolwiek przetwarzania.

Adnotacje danych

W ASP.NET internetowym interfejsie API można użyć atrybutów z przestrzeni nazw System.ComponentModel.DataAnnotations , aby ustawić reguły weryfikacji właściwości modelu. Rozważmy następujący model:

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

Jeśli użyto weryfikacji modelu w ASP.NET MVC, powinno to wyglądać znajomo. Wymagany atrybut mówi, że Name właściwość nie może mieć wartości null. Atrybut Range mówi, że Weight musi zawierać się od zera do 999.

Załóżmy, że klient wysyła żądanie POST z następującą reprezentacją JSON:

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

Widać, że klient nie zawiera Name właściwości , która jest oznaczona jako wymagana. Gdy internetowy interfejs API konwertuje Product kod JSON na Product wystąpienie, sprawdza poprawność względem atrybutów weryfikacji. W akcji kontrolera możesz sprawdzić, czy model jest prawidłowy:

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

Walidacja modelu nie gwarantuje, że dane klienta są bezpieczne. W innych warstwach aplikacji może być wymagana dodatkowa walidacja. (Na przykład warstwa danych może wymuszać ograniczenia klucza obcego). Samouczek Using Web API with Entity Framework (Korzystanie z internetowego interfejsu API z platformą Entity Framework ) zawiera omówienie niektórych z tych problemów.

"Under-Posting": W obszarze delegowania występuje, gdy klient pomija niektóre właściwości. Załóżmy na przykład, że klient wysyła następujące elementy:

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

W tym miejscu klient nie określił wartości dla Price elementu lub Weight. Formater JSON przypisuje wartość domyślną zero do brakujących właściwości.

Zrzut ekranu przedstawiający fragment kodu z opcją menu rozwijanego Dot Models (Modele sklepu produktów) z opcjami menu rozwijanego produktu.

Stan modelu jest prawidłowy, ponieważ zero jest prawidłową wartością dla tych właściwości. To, czy jest to problem, zależy od twojego scenariusza. Na przykład w operacji aktualizacji można odróżnić wartości od "zero" do "nie ustawiono". Aby wymusić na klientach ustawienie wartości, ustaw właściwość null i ustaw wymagany atrybut:

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

"Delegowanie nadmierne": klient może również wysłać więcej danych niż oczekiwano. Na przykład:

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

W tym miejscu kod JSON zawiera właściwość ("Color"), która nie istnieje w Product modelu. W takim przypadku formater JSON po prostu ignoruje tę wartość. (Program formatujący XML robi to samo). Nadmierne delegowanie powoduje problemy, jeśli model ma właściwości przeznaczone tylko do odczytu. Na przykład:

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

Nie chcesz, aby użytkownicy zaktualizowali IsAdmin właściwość i podnieśli poziom uprawnień do administratorów! Najbezpieczniejszą strategią jest użycie klasy modelu, która dokładnie pasuje do tego, co klient może wysłać:

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

Uwaga

Wpis w blogu Brada Wilsona "Input Validation vs. Model Validation in ASP.NET MVC" (Walidacja danych wejściowych vs. Walidacja modelu w ASP.NET MVC) zawiera dobrą dyskusję na temat niedostatecznego publikowania i nadmiernego delegowania. Mimo że wpis dotyczy ASP.NET MVC 2, problemy są nadal istotne dla internetowego interfejsu API.

Obsługa błędów walidacji

Internetowy interfejs API nie zwraca automatycznie błędu do klienta w przypadku niepowodzenia walidacji. Do akcji kontrolera należy sprawdzenie stanu modelu i odpowiednie reagowanie.

Można również utworzyć filtr akcji, aby sprawdzić stan modelu przed wywołaniem akcji kontrolera. Poniżej znajduje się kod przykładowy:

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

Jeśli sprawdzanie poprawności modelu nie powiedzie się, ten filtr zwraca odpowiedź HTTP zawierającą błędy walidacji. W takim przypadku akcja kontrolera nie jest wywoływana.

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."
    ]
  }
}

Aby zastosować ten filtr do wszystkich kontrolerów internetowego interfejsu API, dodaj wystąpienie filtru do kolekcji HttpConfiguration.Filters podczas konfiguracji:

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

        // ...
    }
}

Inną opcją jest ustawienie filtru jako atrybutu dla poszczególnych kontrolerów lub akcji kontrolera:

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