Il presente articolo è stato tradotto automaticamente.

Punti dati

Gestione delle convalide di Entity Framework in WCF Data Services

Julie Lerman

Scaricare il codice di esempio

Julie LermanSto scrivendo questo articolo sulla scia della conferenza Microsoft BUILD.Il nucleo di tutte le emozioni a BUILD fu, naturalmente, il nuovo Metro dell'interfaccia utente per Windows 8 che si siede sulla cima del nuovo Runtime di Windows (WinRT).Se sei un geek di dati, potrebbe aver già guardato per vedere quali opzioni esistano per fornire i dati per le applicazioni "Metro style".In questa anteprima precoce, è possibile fornire dati di archiviazione dei file o dal Web.Se si desidera interagire con dati relazionali, basate sul Web opzioni includono XML o JSON via HTTP, socket e servizi.Sul fronte dei servizi, Metro-stile apps fornirà le librerie client per consumare OData, che significa che qualsiasi esperienza che hai oggi lavorando con OData attraverso Microsoft.NET Framework Silverlight o altre librerie client vi darà un grande vantaggio quando si è pronti a consumare OData nelle vostre applicazioni Metro-style.

Con questo in mente, che potrai dedicare questa colonna di lavorare con OData.Il rilascio di Entity Framework (EF) che contiene codice prima e il DbContext ha introdotto una nuova API di convalida.Vi mostrerò come trarre vantaggio di validazione lato server incorporato, quando il tuo modello EF codice prima essere esposta come OData tramite WCF Data Services.

Nozioni di base di convalida API

Si potrebbe già avere familiarità con la configurazione di attributi quali richiesti o MaxLength alle proprietà di classe utilizzando le annotazioni di dati o l'API fluente.Questi attributi possono essere controllati automaticamente la nuova API di convalida."Entity Framework 4.1 Validation" un articolo nel Developer Center di MSDN dati (msdn.microsoft.com/data/gg193959), viene illustrato questo, come pure come applicare regole con l'interfaccia di IValidatableObject e il metodo ValidateEntity.Mentre si potrebbe già essere convalidando le annotazioni di dati e IValidatable­oggetto sul lato client, loro regole possono essere controllate anche sul lato server con qualsiasi logica di ValidateEntity che hai aggiunto.In alternativa, è possibile anche innescare la convalida su richiesta nel codice del server.

Qui, ad esempio, è una semplice classe di persona che utilizza due annotazioni di dati (la prima specifica che la proprietà LastName è obbligatoria e l'altra imposta una lunghezza massima per il campo stringa 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; }
}

Per impostazione predefinita, EF si esibirà convalida quando viene chiamato SaveChanges. Se una di queste regole fallisce, EF genererà un Sys­tem.Da­ta.ENT­ity.DbEntityValidationException — che ha una struttura interessante. Ogni errore di convalida è descritto in un DbValidationError, e DbValidationErrors sono raggruppati per istanza di oggetto in insiemi di EntityValidationErrors.

Ad esempio, Figura 1 mostra un DbEntityValidationException che verrebbe generata se EF rilevato problemi di validazione con due istanze diverse di persona. Il primo oggetto EntityValidationErrors contiene un insieme di DbValidationErrors per una singola istanza di persona dove c'erano due errori: No LastName e il IdentityCard aveva troppi caratteri. La seconda istanza di persona ha avuto un solo problema; Pertanto, vi è un solo DbValidationError nel secondo oggetto EntityValidationErrors.

DbEntityValidationException Contains Grouped Sets of Errors
Figura 1 DbEntityValidationException contiene insiemi raggruppati di Errori

Nell'articolo di dati Developer Center di MSDN ho accennato, ho mostrato l'eccezione passata torna a un modello-vista -­applicazione Controller (MVC) che ha saputo scoprire e visualizzare gli errori specifici.

In un'applicazione distribuita, tuttavia, gli errori potrebbero non rendere torna a lato client per essere usato e riferito così facilmente. Mentre l'eccezione di primo livello possa essere restituito, l'applicazione client può avere idea come forare in un DbEntityValidationException per trovare gli errori. Con molte applicazioni, lei non può anche avere accesso per lo spazio dei nomi System.Data.Entity e pertanto nessuna conoscenza della DbEntityValidationException.

Più problematico è come WCF Data Services trasmette le eccezioni per impostazione predefinita. Sul lato client, si ottiene solo un messaggio che ti dice "errore durante l'elaborazione di questa richiesta." Ma la critica frase qui è "di"default. È possibile personalizzare i servizi WCF dati per analizzare DbEntityValidationExceptions e restituire le informazioni sull'errore utile al client. Questo è ciò che mi concentrerò su per il resto di questo articolo.

WCF dati servizio risultati nascondere gli errori di convalida per impostazione predefinita

Il mio modello è ospitato in uno strato di dati DbContext ho chiamato PersonModelContext:

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

Ho un servizio di dati semplici che espone il tipo di persona da questo contesto per leggere e scrivere:

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

Perché sto utilizzando codice prima, avrei dovuto fare alcuni ritocchi per ottenere WCF Data Services per lavorare con essa. Invece di tweaking, io ho sostituito il Microsoft.NET Framework 4 System.Data.Services e System.Data.ClientServices con le librerie Microsoft.Data.Services e Microsoft.Data.ClientServices dal marzo 2011 WCF Data Services CTP (vedere bit.ly/mTI69m), che ha quei ritocchi, costruiti nel. Ecco perché il DataServiceProtocolVersion è impostata su V3.

Infine, sto consumando il servizio con una semplice console app che utilizza il seguente metodo per inserire una persona:

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

Si noti che ho trascurato per impostare la proprietà LastName. Poiché LastName è configurato per essere richiesto, l'EF genererà un accogl­tion al servizio dati, ma l'applicazione console riceveranno solo un DataServiceRequestException con il messaggio descritto precedente ("errore durante l'elaborazione di questa richiesta."). Se drill in eccezione interna, troverete che contiene lo stesso messaggio e senza ulteriori dettagli.

WCF Data Services hanno un'impostazione per farvi inviare messaggi di eccezione posteriore con maggiori dettagli aggiungendo il seguente al metodo InitializeService:

#if DEBUG
  config.UseVerboseErrors = true;
#endif

Ora il messaggio interiore (contenuto nella risposta XML dal servizio) ti dice: "Convalida non è riuscita per una o più entità. Vedere la proprietà 'EntityValidationErrors' per maggiori dettagli". Ma, purtroppo, il EntityValidationErrors non ottenere passato torna con l'eccezione. Così si sa che l'API di convalida trovato uno o più problemi, ma non può scoprire qualcosa di più sull'errore. Si noti che ho avvolto UseVerboseErrors in una direttiva del compilatore. UseVerboseErrors deve essere utilizzato solo per il debug — non volete nel vostro codice di produzione.

L'override del metodo HandleException

WCF Data Services espone un metodo virtuale di (Overrideable) chiamato HandleException. Ciò consente di acquisire qualsiasi eccezione che si verifica nel servizio, analizzarla e costruire il proprio DataServiceException per restituire al chiamante. È in questo metodo, che consente di analizzare da eventuali errori di convalida e restituire informazioni più significative all'applicazione chiamante. La firma del metodo è:

protected override void HandleException(HandleExceptionArgs args)

Il tipo HandleExceptionArgs ha un numero di proprietà: Eccezione, ResponseContentType, esponseStatusCode, risposta­scritto, UseVerboseErrors

Di interesse per me è la proprietà Exception. Questo viene generato dove è possibile catturare e identificare le eccezioni dall'API di convalida — DbEntityValidationException. È inoltre possibile gestire qualsiasi altri tipi di errori qui, ma mi concentrerò su cercando e l'analisi le eccezioni di convalida. I've got lo spazio dei nomi System.Data.Entity.Validation nel mio usando dichiarazioni all'inizio della classe in modo che io non devo fortemente digitare l'eccezione.

Inizierò con la presunzione che viene convalidata solo una singola entità, motivo per cui sto solo eseguendo una query per la prima entità­ValidationErrors contenute nell'eccezione, come mostrato nella Figura 2. Se si desidera che il servizio per convalidare più oggetti, assicurarsi di utilizzare il parametro SaveChangesOptions.Batch quando si chiama SaveChanges. In caso contrario, solo un oggetto sarà salvato e convalidato in un momento e una volta che si preme un errore, non più oggetti saranno salvati o convalidati.

Figura 2 la costruzione di un messaggio di eccezione più utile

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

Ciò che sta accadendo in questo metodo è che prima controllare per vedere se l'eccezione è il tipo generato dall'API di convalida. Se è, tirare l'eccezione nella variabile "ex". Successivamente, eseguire query per un elenco di tutti i DbValidationErrors contenute nel primo set di EntityValidationErrors nell'eccezione. Poi ho costruire una nuova stringa di errore utilizzando la proprietà ErrorMessage di ogni EntityValidationError e passare tale stringa torna all'applicazione chiamante in un nuovi dati­ServiceException. EntityValidationError ha altre proprietà, ma esso si basa su un messaggio di errore completo utilizzando il nome della proprietà e il problema di convalida nel ErrorMessage. Con il primo codice è possibile specificare un messaggio di errore personalizzato, ma io sono felice con le impostazioni predefinite ai fini di questa dimostrazione. In questo esempio, il messaggio è "il campo cognome è obbligatorio". Si noti che il costruttore per DataServiceException ha un numero di overload. Io sto mantenendolo semplice fornendo solo il codice 500 "internal server error" e una stringa con il messaggio che voglio relè.

L'analisi della nuova eccezione sul Client

Ora, sul lato client, ancora otterrete un'eccezione che dice "errore durante l'elaborazione di questa richiesta", ma questa volta che l'eccezione interna contiene il messaggio "il campo cognome è obbligatorio."

Ma non è una stringa semplice. Il messaggio di un DataServiceRequestException è formattato in una risposta HTTP, perché la richiesta viene effettuata su 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>

Uno degli overload per DataServiceException costruito nel servizio consente di inserire i codici di errore personalizzati. Se avevo usato che, il codice personalizzato mostrerebbero in <code> elemento dell'errore. Se stai chiamando il servizio da un'applicazione Web, è possibile visualizzare la risposta HTTP direttamente nell'interfaccia utente. In caso contrario, probabilmente vorrete analizzarlo in modo che è possibile gestire l'eccezione utilizzando qualunque modelli che si sta utilizzando nell'applicazione per trattare con gli errori.

Sto utilizzando LINQ to XML per estrarre il messaggio e quindi posso visualizzare nella mia applicazione console. Io chiamo SaveChanges in un blocco try/catch, l'analisi e la visualizzazione del messaggio di errore (vedere Figura 3). Figura 4 mostra i risultati dell'eccezione sul lato client.

Figura 3 l'analisi e la visualizzazione del messaggio di errore restituito dal servizio

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 analizzato il messaggio di errore visualizzato nel Client

Ora mi lancerò un'altra chiave nel metodo InsertPerson. Oltre a trascurare la proprietà LastName, metterò troppi caratteri nell'identità­scheda Proprietà. Ricorda che questa proprietà è stata configurata per avere un MaxLength di 10:

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

Ora il metodo HandleException troverà DataValidation due­Errori per l'istanza di persona che il servizio ha tentato di aggiornamento. Classe StringBuilder conterrà un messaggio di due righe — uno descrivendo il problema con la proprietà LastName e un altro per spiegare il problema con la proprietà IdentityCard.

Nell'applicazione console, questo sarà visto come un singolo messaggio nell'eccezione:

<?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>

LINQ to XML parser inoltreranno poi il messaggio alla console, come mostrato nella Figura 5.

Console App Displaying Multiple Errors for a Single Entity
Figura 5 Console App visualizzazione errori multipli per una singola entità

Beneficiare di convalida anche quando disconnesso

Ora hai visto come, usando un semplice insieme di requisiti applicato utilizzando codice prima le annotazioni di dati, catturare e analizzare le eccezioni di convalida EF, li riporta a un client e, sul lato client, analizzare l'eccezione restituita tramite HTTP. Se si sta lavorando con le regole di convalida applicate mediante configurazioni di proprietà o regole più complesse che è possibile specificare con IValidationObject o eseguendo l'override del metodo ValidateEntity, EF restituirà sempre DbEntity­ValidationExceptions. Ora so come analizzare attraverso quelli e può espandere la logica per ospitare più oggetti, fornire messaggi di errore contenente ulteriori dettagli e gestirli sul server o sul client come richiesto dall'applicazione.

Poiché WCF Data Services restituisce OData, è possibile utilizzare questi servizi e sfruttare la convalida oggi e la pratica così che si può essere pronti a fare lo stesso con tecnologie future della metropolitana-style.

Julie Lerman è un Microsoft MVP,.NET mentore e consulente che vive nelle colline del Vermont. È possibile trovare la sua presentazione sull'accesso ai dati e altri Microsoft.NET argomenti a gruppi di utenti e conferenze in tutto il mondo. Blog di lei a /blog ed è autore del libro altamente acclamato, "Programming Entity Framework" (o ' Reilly Media, 2010). Lei seguire su Twitter a twitter.com/julielerman.

Grazie all'esperto tecnica seguente per la revisione di questo articolo: Mike Flasko