Este artigo foi traduzido por máquina.

Pontos de dados

Lidando com validações do Entity Framework no WCF Data Services

Julie Lerman

Baixar o exemplo de código

Julie Lerman
Eu estou escrevendo esta coluna sobre o calcanhar da conferência Microsoft BUILD. O núcleo de toda a emoção na compilação foi, naturalmente, o novo Metro da interface do usuário para Windows 8 que fica em cima do Runtime do novo Windows (WinRT). Se você é um geek de dados, você pode já olhei para ver quais opções existem para fornecer dados para aplicações de "Estilo Metro". Esta visualização antecipada, você pode fornecer dados de armazenamento de arquivos ou da Web. Se você deseja interagir com dados relacionais, baseado na Web as opções incluem XML ou JSON sobre HTTP, soquetes e serviços. Em matéria de serviços, Metro-estilo apps fornecerá as bibliotecas de cliente para o consumo de OData, que significa que qualquer experiência que você tem hoje trabalhando com OData através da Microsoft.NET Framework, Silverlight ou outras bibliotecas de cliente lhe dará uma grande vantagem quando você estiver pronto para consumir OData em seus aplicativos de Metro-estilo.

Com isso em mente, eu vou dedicar esta coluna para trabalhar com OData. O lançamento de entidade Framework (EF) que contém código primeiro e a DbContext introduziu uma nova API de validação. Vou mostrar-lhe como tirar proveito da validação do lado do servidor interno quando seu EF código primeiro modelo está sendo exposto como OData através de serviços de dados do WCF.

Noções básicas de validação API

Você pode já estar familiarizado com configurando atributos tais como necessários ou MaxLength para propriedades de classe usando anotações de dados ou a API fluente. Esses atributos podem ser feitos automaticamente a nova API de validação. "Entidade quadro 4.1 validação," um artigo na MSDN dados Developer Center (msdn.microsoft.com/data/gg193959), demonstra isso, além de como aplicar as regras com a interface de IValidatableObject e o Método ValidateEntity. Enquanto você já pode validar dados anotações e IValidatable­de objeto no lado do cliente, suas regras também podem ser verificadas no lado do servidor, juntamente com qualquer lógica de ValidateEntity que você adicionou. Como alternativa, você também pode escolher acionar a validação sob demanda em seu código de servidor.

Aqui, por exemplo, é uma classe pessoa simple que usa dois dados anotações (o primeiro especifica que a propriedade LastName é necessária e o outro define um comprimento máximo para o campo de Cadeia de caracteres IdentityCard):

public class Person
{
  public int PersonId { get; set; }
  public string FirstName { get; set; }
  [Required]
  public string LastName { get; set; }
  [MaxLength(10)]
  public string IdentityCardNumber { get; set; }
}

Por padrão, EF irá executar a validação quando SaveChanges é chamado. Se qualquer uma destas regras falhar, EF lançará um Sys­tem.Da­ta.Ent­ity.DbEntityValidationException — que tem uma estrutura interessante. Cada erro de validação é descrito em um DbValidationError, e DbValidationErrors são agrupados por instância do objeto em conjuntos de EntityValidationErrors.

Por exemplo, Figura 1 mostra um DbEntityValidationException que seria lançada se EF detectados problemas de validação com duas instâncias diferentes de pessoa. O primeiro objeto EntityValidationErrors contém um conjunto de DbValidationErrors para uma instância única pessoa onde havia dois erros: nenhum sobrenome e o IdentityCard tinham muitos caracteres. A segunda instância de pessoa teve um único problema; Portanto, há apenas um DbValidationError no segundo objeto EntityValidationErrors.

DbEntityValidationException Contains Grouped Sets of Errors
Figura 1 DbEntityValidationException contém conjuntos agrupados de erros

No artigo do MSDN dados Developer Center eu mencionei, eu mostrei a exceção sendo passada de volta para um modelo-visão -­aplicativo Controller (MVC) que soube descobrir e exibir os erros específicos.

Em um aplicativo distribuído, no entanto, os erros podem não fazê-lo voltar para o lado do cliente a ser usado e relatadas tão facilmente. Enquanto a exceção de nível superior pode ser retornada, o aplicativo cliente não pode ter nenhuma idéia como detalhar um DbEntityValidationException para encontrar os erros. Com muitas aplicações, não ainda terá acesso ao namespace System.Data.Entity e, portanto, nenhum conhecimento do DbEntityValidationException.

Mais problemático é como o WCF Data Services transmite exceções por padrão. No lado do cliente, você só obter uma mensagem informando que "um erro ocorreu ao processar este pedido". Mas a frase crítica aqui é "por"padrão. Você pode personalizar seus serviços de dados do WCF para analisar DbEntityValidationExceptions e retornar informações de erro útil para o cliente. Isso é o que eu vou focar para o restante desta coluna.

Erros de validação de ocultar WCF resultados de serviço de dados por padrão

Meu modelo é hospedado em uma camada de dados DbContext eu tenho chamado PersonModelContext:

public class PersonModelContext : DbContext
  {
    public DbSet<Person> People { get; set; }
  }

Eu tenho um serviço de dados simples que apresenta o tipo de pessoa neste contexto para leitura e escrita:

public class DataService : DataService<PersonModelContext>
{
  public static void InitializeService(DataServiceConfiguration config)
  {
    config.SetEntitySetAccessRule("People", EntitySetRights.All);
    config.DataServiceBehavior.MaxProtocolVersion =
      DataServiceProtocolVersion.V3;
  }
}

Porque eu estou usando o código primeiro, eu teria que fazer alguns ajustes para obter os serviços de dados do WCF para trabalhar com ele. Em vez de ajustes, troquei a Microsoft.NET Framework 4 System.Data.Services e System.Data.ClientServices com as bibliotecas Microsoft.Data.Services e Microsoft.Data.ClientServices de Março de 2011 WCF Data Services CTP (ver bit.ly/mTI69m), que tem essas emendas construídas em. É por isso que o DataServiceProtocolVersion é definido como V3.

Finalmente, eu estou consumindo o serviço com um aplicativo de console simples que usa o método a seguir para inserir uma pessoa:

private static void InsertPersonNoLastName()
{
  var person = new Person
  {
    FirstName = "Julie",
    IdentityCardNumber="123456789",
  };
  var context = new PersonModelContext
   (new Uri("http://localhost:43447/DataService.svc"));
 context.AddToPeople(person);
 context.SaveChanges();
}

Observe que eu tenho negligenciado definir a propriedade LastName. Como sobrenome é configurado para ser necessária, o EF lançará uma ceção­tion para o serviço de dados, mas o aplicativo de console apenas receberá um DataServiceRequestException com a mensagem descrita anteriormente ("Ocorreu um erro ao processar essa solicitação."). Se você detalhar a exceção interna, você encontrará que contém a mesma mensagem e sem detalhes adicionais.

Serviços de dados de WCF tem uma configuração para permitir que você envie mensagens de exceção de volta com mais detalhes, adicionando o seguinte para o método InitializeService:

#if DEBUG
  config.UseVerboseErrors = true;
#endif

Agora a mensagem interna (contida na resposta do serviço XML) diz-lhe: "validação falha para uma ou mais entidades. Consulte Propriedade 'EntityValidationErrors' para obter mais detalhes". Mas, infelizmente, os EntityValidationErrors não obter passados volta com a exceção. Então você sabe que a API de validação encontrados um ou mais problemas, mas você não pode descobrir algo mais sobre o erro. Observe que eu envolto UseVerboseErrors em uma diretiva de compilador. UseVerboseErrors só deve ser usado para depuração — você não a quer no seu código de produção.

Substituir o método HandleException

Serviços de dados do WCF apresenta um método (Overrideable) virtual chamado HandleException. Isso permite que você capture qualquer exceção que acontece no serviço, analisá-lo e construir seu próprio DataServiceException para retornar para o chamador. É neste método que você pode analisar quaisquer erros de validação e retornar informações mais significativas para o aplicativo de chamada. A assinatura do método é:

protected override void HandleException(HandleExceptionArgs args)

O tipo de HandleExceptionArgs tem um número de propriedades: Exception, ResponseContentType, esponseStatusCode, resposta­escrito, UseVerboseErrors

De interesse para mim é a propriedade Exception. Isto é onde você pode capturar e identificar exceções Descartado pela API de validação — DbEntityValidationException. Você também pode lidar com quaisquer outros tipos de erros aqui, mas irá focar procurando e analisando as exceções de validação. Eu tenho o namespace System.Data.Entity.Validation no meu usando instruções na parte superior da classe para que eu não tenho que fortemente digite a exceção.

Eu vou começar com a presunção de que está sendo validada apenas uma única entidade, razão pela qual eu estou consultando apenas para a primeira entidade­ValidationErrors contidas na exceção, como mostrado na Figura 2. Se você quiser que o serviço para validar vários objetos, certifique-se de usar o parâmetro SaveChangesOptions quando você chama SaveChanges. Caso contrário, apenas um objeto será salvo e validado por vez e uma vez que você bateu um erro, não mais objetos serão salvas ou validados.

Figura 2 Criando uma mensagem de exceção mais útil

protected override void HandleException(HandleExceptionArgs args)
{
  if (args.Exception.GetType()==
    typeof(DbEntityValidationException))
  {
    var ex=args.Exception as DbEntityValidationException;
    var errors = ex.EntityValidationErrors.First().ValidationErrors.ToList();
    var errorMessage=new StringBuilder();
    foreach (System.Data.Entity.Validation.DbValidationError e in errors)
    {
      errorMessage.AppendLine(e.ErrorMessage);
    }
    args.Exception = new DataServiceException(500, errorMessage.ToString());
  }
}

O que está acontecendo neste método é primeiro verifique para ver se a exceção é o tipo lançado pela API de validação. Se for, eu puxar a exceção para a variável "ex". Em seguida, eu consultar para obter uma lista de todos os DbValidationErrors contidos no primeiro conjunto de EntityValidationErrors na exceção. Então eu construir uma nova cadeia de erro usando a propriedade ErrorMessage de cada EntityValidationError e passar essa Cadeia de caracteres de volta para o aplicativo de chamada em um novos dados­ServiceException. EntityValidationError tem outras propriedades, mas cria uma mensagem de erro completa usando o nome da propriedade e o problema de validação para o ErrorMessage. Com o primeiro código você pode especificar uma mensagem de erro personalizada, mas estou feliz com os padrões para os fins desta demonstração. Neste exemplo, a mensagem é "O campo LastName é necessário". Observe que o construtor para DataServiceException tem várias sobrecargas. Eu estou mantendo-o simples, fornecendo apenas o código 500 "erro interno do servidor" e uma Cadeia de caracteres com a mensagem que eu desejar retransmitir.

Analisar a nova exceção no cliente

Agora, no lado do cliente, você ainda terá uma exceção que diz "Ocorreu um erro ao processar este pedido", mas desta vez que a exceção interna contém a mensagem "O campo LastName é necessário."

Mas não é uma simple Cadeia de caracteres. A mensagem de um DataServiceRequestException é formatada em uma resposta HTTP porque a solicitação é feita por HTTP:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns=
  "https://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <code></code>
  <message xml:lang="en-US">The LastName field is required.&#xD;
  </message>
</error>

Uma das sobrecargas para o DataServiceException I construiu no serviço permite-lhe inserir códigos de erro personalizado. Se eu tivesse usado que, o código personalizado iria aparecer em <code> elemento do erro. Se você estiver ligando o serviço de um aplicativo Web, você pode ser capaz de exibir a resposta HTTP diretamente em sua interface do usuário. Caso contrário, você provavelmente vai querer analisá-lo para que você pode manipular a exceção usando quaisquer padrões que você está usando em seu aplicativo para lidar com erros.

Estou usando o LINQ para XML para extrair a mensagem e, em seguida, possa exibi-lo em meu aplicativo do console. Eu chamo SaveChanges em um bloco try/catch, analisar e exibir a mensagem de erro (consulte Figura 3). Figura 4 mostra os resultados da exceção do lado do cliente.

Figura 3 analisar e exibir a mensagem de erro retornada do serviço

try
{
  context.SaveChanges();
}
catch (Exception ex)
{
  var sr = new StringReader(ex.InnerException.Message);
  XElement root = XElement.Load(sr);
  IEnumerable<XElement> message =
    from el in root.Elements()
    where el.Name.LocalName == "message"
    select el;
  foreach (XElement el in message)
    Console.WriteLine(el.Value);
  Console.ReadKey();
}

Parsed Error Message Displayed in the Client
Figura 4 analisado a mensagem de erro exibida no cliente

Agora eu vou jogar outra chave inglesa para o método InsertPerson. Além de negligenciar a propriedade LastName, eu vou colocar muitos caracteres para a identidade­propriedade do cartão. Lembre-se que esta propriedade foi configurada para ter um MaxLength de 10:

var person = new Person
{
  FirstName = "Julie",
  IdentityCardNumber="123456789ABCDE"
};

Agora o método HandleException vai encontrar dois DataValidation­erros para a instância da pessoa que o serviço tentou atualizar. O StringBuilder irá conter uma mensagem de duas linhas — um descrevendo o problema com a propriedade LastName e outro para explicar o problema com a propriedade IdentityCard.

No aplicativo do console, isso será visto como uma única mensagem na exceção:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns=
  "https://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <code></code>
  <message xml:lang="en-US">The field IdentityCardNumber must be a string or 
     array type with a maximum length of '10'.&#xD;
    The LastName field is required.&#xD;
  </message>
</error>

O LINQ para XML parser irá retransmitir a mensagem para o console, conforme mostrado na Figura 5.

Console App Displaying Multiple Errors for a Single Entity
Figura 5 Console App mostrando vários erros para uma única entidade

Beneficiar de validação mesmo quando desconectado

Agora você já viu como, usando um simple conjunto de requisitos aplicadas usando anotações de dados primeiro código, você pode capturar e analisar exceções de validação EF, devolvê-los para um cliente e, no lado do cliente, analisar a exceção retornada através de HTTP. Se você estiver trabalhando com regras de validação aplicadas por meio de configurações de propriedade ou regras mais complexas que você pode especificar com IValidationObject ou substituindo o Método ValidateEntity, o EF sempre retornará DbEntity­ValidationExceptions. Agora você sabe como analisar por aqueles e pode expandir a lógica para acomodar vários objetos, fornecer mensagens de erro com mais detalhes e tratá-los no servidor ou no cliente conforme exigido pelo seu aplicativo.

Porque os serviços de dados do WCF retorna OData, você pode consumir esses serviços e aproveitar a validação hoje e prática para que você possa estar pronto para fazer o mesmo com futuras tecnologias Metro-estilo.

Julie Lerman é um Microsoft MVP,.Mentor líquido e consultor que vive nas colinas de Vermont. Você pode encontrar sua apresentação sobre acesso a dados e outro Microsoft.NET tópicos em grupos de usuários e conferências em todo o mundo. She blogs at thedatafarm.com e é o autor do aclamado livro, "Programação Entity Framework" (o ' Reilly Media, 2010). Segui-la no Twitter em twitter.com/julielerman.

Graças ao seguinte especialista técnico para revisão deste artigo: Mike Flasko