Поделиться через


Проверка модели в веб-API ASP.NET

В этой статье показано, как добавлять заметки к моделям, использовать заметки для проверки данных и обрабатывать ошибки проверки в веб-API. Когда клиент отправляет данные в веб-API, часто требуется проверить данные перед выполнением какой-либо обработки.

Заметки к данным

В веб-API ASP.NET можно использовать атрибуты из пространства имен System.ComponentModel.DataAnnotations, чтобы задать правила проверки свойств модели. Рассмотрим следующую модель:

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

Если вы использовали проверку модели в ASP.NET MVC, это должно выглядеть знакомо. Атрибут Required указывает, что Name свойство не должно иметь значение NULL. Атрибут Range указывает, что Weight значение должно находиться в диапазоне от нуля до 999.

Предположим, что клиент отправляет запрос POST со следующим представлением JSON:

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

Вы видите, что клиент не включил Name свойство , которое помечено как обязательное. Когда веб-API преобразует JSON в Product экземпляр, он проверяет по Product атрибутам проверки. В действии контроллера можно проверка, является ли модель допустимой:

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

Проверка модели не гарантирует безопасность данных клиента. На других уровнях приложения может потребоваться дополнительная проверка. (Например, уровень данных может применять ограничения внешнего ключа.) Некоторые из этих проблем рассматриваются в руководстве Использование веб-API с Entity Framework .

"Недоопубликовка". Недоставка происходит, когда клиент оставляет некоторые свойства. Например, предположим, что клиент отправляет следующее:

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

Здесь клиент не указал значения для Price или Weight. Модуль форматирования JSON присваивает отсутствующим свойствам значение по умолчанию , равное нулю.

Снимок экрана: фрагмент кода с точечными моделями в магазине продуктов в раскрывающемся меню Продукта.

Состояние модели является допустимым, так как ноль является допустимым значением для этих свойств. Проблема зависит от вашего сценария. Например, в операции обновления может потребоваться различать "ноль" и "не задано". Чтобы принудительно задать значение клиентами, сделайте свойство допускаемым значением NULL и задайте атрибут Required :

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

"Чрезмерная публикация": клиент также может отправить больше данных, чем вы ожидали. Пример:

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

Здесь JSON содержит свойство ("Цвет"), которое не существует в Product модели. В этом случае форматировщик JSON просто игнорирует это значение. (То же самое делает модуль форматирования XML.) Чрезмерная публикация вызывает проблемы, если модель имеет свойства, которые вы намеревались использовать только для чтения. Пример:

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

Вы не хотите, чтобы пользователи IsAdmin обновляли свойство и повышали уровень до администраторов! Наиболее безопасной стратегией является использование класса модели, который точно соответствует тому, что клиент может отправлять:

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

Примечание

Брэд Уилсон в блоге "Проверка входных данных против проверки модели в ASP.NET MVC" имеет хорошее обсуждение недо-публикации и чрезмерной публикации. Хотя эта запись посвящена ASP.NET MVC 2, эти проблемы по-прежнему актуальны для веб-API.

Обработка ошибок проверки

Веб-API не возвращает клиенту автоматически ошибку при сбое проверки. Действия контроллера должны проверка состояние модели и соответствующим образом реагировать.

Вы также можете создать фильтр действий, чтобы проверка состояние модели перед вызовом действия контроллера. Пример кода приведен ниже.

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

Если проверка модели завершается сбоем, этот фильтр возвращает HTTP-ответ, содержащий ошибки проверки. В этом случае действие контроллера не вызывается.

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

Чтобы применить этот фильтр ко всем контроллерам веб-API, во время настройки добавьте экземпляр фильтра в коллекцию HttpConfiguration.Filters :

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

        // ...
    }
}

Другой вариант — задать фильтр в качестве атрибута для отдельных контроллеров или действий контроллера:

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