Validação com a interface IDataErrorInfo (C#)
por Stephen Walther
Stephen Walther mostra como exibir mensagens de erro de validação personalizadas implementando a interface IDataErrorInfo em uma classe de modelo.
O objetivo deste tutorial é explicar uma abordagem para executar a validação em um aplicativo MVC ASP.NET. Você aprenderá a impedir que alguém envie um formulário HTML sem fornecer valores para os campos de formulário necessários. Neste tutorial, você aprenderá a executar a validação usando a interface IErrorDataInfo.
Suposições
Neste tutorial, usarei o banco de dados MoviesDB e a tabela de banco de dados Filmes. Essa tabela tem as seguintes colunas:
Nome da Coluna | Tipo de Dados | Permitir Nulos |
---|---|---|
ID | int | Falso |
Title | Nvarchar(100) | Falso |
Diretor | Nvarchar(100) | Falso |
DateReleased | Datetime | Falso |
Neste tutorial, uso o Microsoft Entity Framework para gerar minhas classes de modelo de banco de dados. A classe Movie gerada pelo Entity Framework é exibida na Figura 1.
Figura 01: a entidade Movie (clique para exibir a imagem em tamanho real)
Observação
Para saber mais sobre como usar o Entity Framework para gerar suas classes de modelo de banco de dados, confira meu tutorial intitulado Criando classes de modelo com o Entity Framework.
A classe Controller
Usamos o controlador Home para listar filmes e criar novos filmes. O código dessa classe está contido na Listagem 1.
Listagem 1 – Controllers\HomeController.cs
using System.Linq;
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
private MoviesDBEntities _db = new MoviesDBEntities();
public ActionResult Index()
{
return View(_db.MovieSet.ToList());
}
public ActionResult Create()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Movie movieToCreate)
{
// Validate
if (!ModelState.IsValid)
return View();
// Add to database
try
{
_db.AddToMovieSet(movieToCreate);
_db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
}
A classe de controlador Home na Listagem 1 contém duas ações Create(). A primeira ação exibe o formulário HTML para criar um novo filme. A segunda ação Create() executa a inserção real do novo filme no banco de dados. A segunda ação Create() é invocada quando o formulário exibido pela primeira ação Create() é enviado ao servidor.
Observe que a segunda ação Create() contém as seguintes linhas de código:
// Validate
if (!ModelState.IsValid)
return View();
A propriedade IsValid retorna false quando há um erro de validação. Nesse caso, o modo de exibição Criar que contém o formulário HTML para criar um filme é reproduzido novamente.
Criando uma classe parcial
A classe Movie é gerada pelo Entity Framework. Você poderá ver o código da classe Movie se expandir o arquivo MoviesDBModel.edmx na janela Gerenciador de Soluções e abrir o MoviesDBModel.Designer. arquivo cs no Editor de Códigos (consulte a Figura 2).
Figura 02: o código da entidade Movie (Clique para exibir a imagem em tamanho real)
A classe Movie é parcial. Isso significa que podemos adicionar outra classe parcial com o mesmo nome para estender a funcionalidade da classe Movie. Adicionaremos nossa lógica de validação à nova classe parcial.
Adicione a classe na Listagem 2 à pasta Modelos.
Listagem 2 – Models\Movie.cs
using System.Collections.Generic;
using System.ComponentModel;
namespace MvcApplication1.Models
{
public partial class Movie
{
}
}
Observe que a classe na Listagem 2 inclui o modificador parcial . Todos os métodos ou propriedades que você adicionar a essa classe se tornam parte da classe Movie gerada pelo Entity Framework.
Adicionando métodos parciais OnChanging e OnChanged
Quando o Entity Framework gera uma classe de entidade, o Entity Framework adiciona métodos parciais à classe automaticamente. O Entity Framework gera métodos parciais OnChanging e OnChanged que correspondem a cada propriedade da classe .
No caso da classe Movie, o Entity Framework cria os seguintes métodos:
- OnIdChanging
- OnIdChanged
- OnTitleChanging
- OnTitleChanged
- OnDirectorChanging
- OnDirectorChanged
- OnDateReleasedChanging
- OnDateReleasedChanged
O método OnChanging é chamado logo antes da propriedade correspondente ser alterada. O método OnChanged é chamado logo após a propriedade ser alterada.
Você pode aproveitar esses métodos parciais para adicionar a lógica de validação à classe Movie. A classe update Movie na Listagem 3 verifica se as propriedades Title e Director recebem valores não vazios.
Observação
Um método parcial é um método definido em uma classe que você não precisa implementar. Se você não implementar um método parcial, o compilador removerá a assinatura do método e todas as chamadas para o método para que não haja custos de tempo de execução associados ao método parcial. No Editor de Visual Studio Code, você pode adicionar um método parcial digitando o palavra-chave parcial seguido por um espaço para exibir uma lista de parciais a serem implementadas.
Listagem 3 – Models\Movie.cs
using System.Collections.Generic;
using System.ComponentModel;
namespace MvcApplication1.Models
{
public partial class Movie : IDataErrorInfo
{
private Dictionary<string, string> _errors = new Dictionary<string, string>();
partial void OnTitleChanging(string value)
{
if (value.Trim().Length == 0)
_errors.Add("Title", "Title is required.");
}
partial void OnDirectorChanging(string value)
{
if (value.Trim().Length == 0)
_errors.Add("Director", "Director is required.");
}
}
}
Por exemplo, se você tentar atribuir uma cadeia de caracteres vazia à propriedade Title, uma mensagem de erro será atribuída a um Dicionário chamado _errors.
Neste ponto, nada realmente acontece quando você atribui uma cadeia de caracteres vazia à propriedade Title e um erro é adicionado ao campo de _errors privado. Precisamos implementar a interface IDataErrorInfo para expor esses erros de validação à estrutura ASP.NET MVC.
Implementando a interface IDataErrorInfo
A interface IDataErrorInfo faz parte do .NET Framework desde a primeira versão. Essa interface é uma interface muito simples:
public interface IDataErrorInfo
{
string this[string columnName] { get; }
string Error { get; }
}
Se uma classe implementar a interface IDataErrorInfo, a estrutura ASP.NET MVC usará essa interface ao criar uma instância da classe . Por exemplo, a ação Criar() do controlador Inicial aceita uma instância da classe Movie:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Movie movieToCreate)
{
// Validate
if (!ModelState.IsValid)
return View();
// Add to database
try
{
_db.AddToMovieSet(movieToCreate);
_db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
A estrutura ASP.NET MVC cria a instância do Filme passada para a ação Create() usando um associador de modelo (o DefaultModelBinder). O associador de modelo é responsável por criar uma instância do objeto Movie associando os campos de formulário HTML a uma instância do objeto Movie.
O DefaultModelBinder detecta se uma classe implementa ou não a interface IDataErrorInfo. Se uma classe implementar essa interface, o associador de modelo invocará o IDataErrorInfo.this indexer para cada propriedade da classe. Se o indexador retornar uma mensagem de erro, o associador de modelo adicionará essa mensagem de erro ao estado do modelo automaticamente.
O DefaultModelBinder também verifica a propriedade IDataErrorInfo.Error. Essa propriedade destina-se a representar erros de validação não específicos de propriedade associados à classe . Por exemplo, talvez você queira impor uma regra de validação que dependa dos valores de várias propriedades da classe Movie. Nesse caso, você retornaria um erro de validação da propriedade Error.
A classe Movie atualizada na Listagem 4 implementa a interface IDataErrorInfo.
Listagem 4 – Models\Movie.cs (implementa IDataErrorInfo)
using System.Collections.Generic;
using System.ComponentModel;
namespace MvcApplication1.Models
{
public partial class Movie : IDataErrorInfo
{
private Dictionary<string, string> _errors = new Dictionary<string, string>();
partial void OnTitleChanging(string value)
{
if (value.Trim().Length == 0)
_errors.Add("Title", "Title is required.");
}
partial void OnDirectorChanging(string value)
{
if (value.Trim().Length == 0)
_errors.Add("Director", "Director is required.");
}
#region IDataErrorInfo Members
public string Error
{
get
{
return string.Empty;
}
}
public string this[string columnName]
{
get
{
if (_errors.ContainsKey(columnName))
return _errors[columnName];
return string.Empty;
}
}
#endregion
}
}
Na Listagem 4, a propriedade do indexador verifica a coleção _errors para ver se ela contém uma chave que corresponde ao nome da propriedade passada para o indexador. Se não houver nenhum erro de validação associado à propriedade, uma cadeia de caracteres vazia será retornada.
Você não precisa modificar o controlador Home de forma alguma para usar a classe Movie modificada. A página exibida na Figura 3 ilustra o que acontece quando nenhum valor é inserido para os campos de formulário Título ou Diretor.
Figura 03: um formulário com valores ausentes (clique para exibir a imagem em tamanho real)
Observe que o valor DateReleased é validado automaticamente. Como a propriedade DateReleased não aceita valores NULL, o DefaultModelBinder gera um erro de validação para essa propriedade automaticamente quando não tem um valor. Se você quiser modificar a mensagem de erro para a propriedade DateReleased, precisará criar um associador de modelo personalizado.
Resumo
Neste tutorial, você aprendeu a usar a interface IDataErrorInfo para gerar mensagens de erro de validação. Primeiro, criamos uma classe Movie parcial que estende a funcionalidade da classe Movie parcial gerada pelo Entity Framework. Em seguida, adicionamos a lógica de validação aos métodos parciais OnTitleChanging() e OnDirectorChanging() da classe Movie. Por fim, implementamos a interface IDataErrorInfo para expor essas mensagens de validação à estrutura MVC ASP.NET.