Share via


Validação de modelo na ASP.NET Web API

Este artigo mostra como anotar seus modelos, usar as anotações para validação de dados e tratar erros de validação em sua API Web. Quando um cliente envia dados para sua API Web, geralmente você deseja validar os dados antes de fazer qualquer processamento.

Anotações de dados

Em ASP.NET Web API, você pode usar atributos do namespace System.ComponentModel.DataAnnotations para definir regras de validação para propriedades em seu modelo. Considere o modelo a seguir:

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

Se você tiver usado a validação de modelo no ASP.NET MVC, isso deverá parecer familiar. O atributo Required diz que a Name propriedade não deve ser nula. O atributo Range diz que Weight deve estar entre zero e 999.

Suponha que um cliente envie uma solicitação POST com a seguinte representação JSON:

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

Você pode ver que o cliente não incluiu a Name propriedade , que está marcada como necessária. Quando a API Web converte o JSON em uma Product instância, ela valida o Product em relação aos atributos de validação. Na ação do controlador, você pode marcar se o modelo é válido:

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

A validação do modelo não garante que os dados do cliente sejam seguros. A validação adicional pode ser necessária em outras camadas do aplicativo. (Por exemplo, a camada de dados pode impor restrições de chave estrangeira.) O tutorial Usando a API Web com o Entity Framework explora alguns desses problemas.

"Sub-Postagem": a sub-postagem ocorre quando o cliente deixa de fora algumas propriedades. Por exemplo, suponha que o cliente envie o seguinte:

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

Aqui, o cliente não especificou valores para Price ou Weight. O formatador JSON atribui um valor padrão de zero às propriedades ausentes.

Captura de tela do snippet de código com as opções de menu suspenso do Produto do ponto modelos de ponto da Loja de Produtos.

O estado do modelo é válido, pois zero é um valor válido para essas propriedades. Se esse é um problema depende do seu cenário. Por exemplo, em uma operação de atualização, talvez você queira distinguir entre "zero" e "não definido". Para forçar os clientes a definir um valor, torne a propriedade anulável e defina o atributo Required :

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

"Over-Posting": um cliente também pode enviar mais dados do que você esperava. Por exemplo:

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

Aqui, o JSON inclui uma propriedade ("Color") que não existe no Product modelo. Nesse caso, o formatador JSON simplesmente ignora esse valor. (O formatador XML faz o mesmo.) O excesso de postagem causará problemas se o modelo tiver propriedades que você pretendia ser somente leitura. Por exemplo:

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

Você não quer que os usuários atualizem a IsAdmin propriedade e se elevem aos administradores! A estratégia mais segura é usar uma classe de modelo que corresponda exatamente ao que o cliente tem permissão para enviar:

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

Observação

A postagem no blog de Brad Wilson "Validação de entrada versus validação de modelo no ASP.NET MVC" tem uma boa discussão sobre sub-postagem e excesso de postagem. Embora a postagem seja sobre ASP.NET MVC 2, os problemas ainda são relevantes para a API Web.

Tratamento de erros de validação

A API Web não retorna automaticamente um erro ao cliente quando a validação falha. Cabe à ação do controlador marcar o estado do modelo e responder adequadamente.

Você também pode criar um filtro de ação para marcar o estado do modelo antes que a ação do controlador seja invocada. O código a seguir mostra um exemplo:

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

Se a validação do modelo falhar, esse filtro retornará uma resposta HTTP que contém os erros de validação. Nesse caso, a ação do controlador não é invocada.

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

Para aplicar esse filtro a todos os controladores de API Web, adicione uma instância do filtro à coleção HttpConfiguration.Filters durante a configuração:

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

        // ...
    }
}

Outra opção é definir o filtro como um atributo em controladores individuais ou ações do controlador:

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