Controllo delle versioni dei contratti dati

Lo sviluppo delle applicazioni implica la modifica dei contratti dati utilizzati dai servizi. In questo argomento viene illustrato come controllare le versioni dei contratti dati. Vengono descritti i meccanismi di controllo delle versioni dei contratti dati. Per una panoramica completa e per materiale sussidiario sul controllo delle versioni, vedere Procedure consigliate: controllo delle versioni del contratto dati.

Modifiche che causano interruzione e modifiche che non causano interruzione

Le modifiche a un contratto dati possono determinare interruzioni oppure no. Quando un contratto dati viene modificato in modo che non determini interruzioni, un'applicazione che utilizza la versione precedente del contratto può comunicare con un'applicazione che utilizza la versione più recente e viceversa. Una modifica che determina interruzioni, invece, può impedire la comunicazione in una o in entrambe le direzioni.

Qualsiasi modifica a un tipo che non influisce sulla modalità con cui viene trasmessa e ricevuta non determina interruzioni. Tali modifiche non cambiano il contratto dati ma solo il tipo sottostante. È possibile, ad esempio, modificare il nome di un campo in modo che non determini interruzioni se si imposta la proprietà Name di DataMemberAttribute sul nome della versione precedente. Nel codice seguente viene illustrata la versione 1 di un contratto dati.

// Version 1
[DataContract]
public class Person
{
    [DataMember]
    private string Phone;
}
' Version 1
<DataContract()> _
Public Class Person
    <DataMember()> _
    Private Phone As String
End Class

Nel codice seguente viene illustrata una modifica che non determina interruzioni.

// Version 2. This is a non-breaking change because the data contract
// has not changed, even though the type has.
[DataContract]
public class Person
{
    [DataMember(Name = "Phone")]
    private string Telephone;
}
' Version 2. This is a non-breaking change because the data contract 
' has not changed, even though the type has.
<DataContract()> _
Public Class Person
    <DataMember(Name:="Phone")> _
    Private Telephone As String
End Class

Alcune modifiche cambiano i dati trasmessi ma possono determinare interruzioni oppure no. Le modifiche seguenti determinano sempre interruzioni:

  • Modifica del valore Name o Namespace di un contratto dati.

  • Modifica dell'ordine di membri dati tramite la proprietà Order di DataMemberAttribute.

  • Ridenominazione di un membro dati.

  • Modifica del contratto dati di un membro dati. La modifica, ad esempio, del tipo di membro dati da un numero intero a una stringa o da un tipo con un contratto dati denominato "Customer" a un tipo con un contratto dati denominato "Person".

Sono possibili inoltre le modifiche seguenti.

Aggiunta e rimozione di membri dati

Nella maggior parte dei casi, l'aggiunta o la rimozione di un membro dati non è una modifica che determina interruzioni, a meno che non venga richiesta una rigorosa conformità allo schema (convalide di nuove istanze a fronte dello schema precedente).

Quando un tipo con un campo aggiuntivo viene deserializzato in un tipo con un campo mancante, le informazioni aggiuntive vengono ignorate. Può essere archiviato anche a fini di sequenze di andata e ritorno. Per ulteriori informazioni, vedere Contratti dati compatibili con versioni successive.

Quando un tipo con un campo mancante viene deserializzato in un tipo con un campo aggiuntivo, il campo aggiuntivo viene lasciato con il valore predefinito, generalmente zero o null. Il valore predefinito può essere modificato. Per ulteriori informazioni, vedere Callback di serializzazione a tolleranza di versione.

È possibile, ad esempio, utilizzare la classe CarV1 in un client e la classe CarV2 in un servizio o è possibile utilizzare la classe CarV1 in un servizio e la classe CarV2 in un client.

// Version 1 of a data contract, on machine V1.
[DataContract(Name = "Car")]
public class CarV1
{
    [DataMember]
    private string Model;
}

// Version 2 of the same data contract, on machine V2.
[DataContract(Name = "Car")]
public class CarV2
{
    [DataMember]
    private string Model;

    [DataMember]
    private int HorsePower;
}
' Version 1 of a data contract, on machine V1.
<DataContract(Name:="Car")> _
Public Class CarV1
    <DataMember()> _
    Private Model As String
End Class

' Version 2 of the same data contract, on machine V2.
<DataContract(Name:="Car")> _
Public Class CarV2
    <DataMember()> _
    Private Model As String

    <DataMember()> _
    Private HorsePower As Integer
End Class

L'endpoint della versione 2 può inviare correttamente dati all'endpoint della versione 1. La serializzazione della versione 2 del contratto dati Car restituisce un XML simile all'esempio seguente.

<Car>  
    <Model>Porsche</Model>  
    <HorsePower>300</HorsePower>  
</Car>  

Il motore di deserializzazione nella versione 1 non trova un membro dati corrispondente al campo HorsePower e ignora quei dati.

L'endpoint della versione 1 può, inoltre, inviare dati all'endpoint della versione 2. La serializzazione della versione 1 del contratto dati Car restituisce un XML simile all'esempio seguente.

<Car>  
    <Model>Porsche</Model>  
</Car>  

Il deserializzatore della versione 2 non sa su quale valore impostare il campo HorsePower, poiché non ci sono dati corrispondenti nel codice XML in ingresso. Il campo viene impostato sul valore predefinito 0.

Membri dati obbligatori

Un membro dati può essere contrassegnato come obbligatorio impostando la proprietà IsRequired di DataMemberAttribute su true. Se dati obbligatori vanno persi durante la deserializzazione, anziché impostare il membro dati sul valore predefinito, viene generata un'eccezione.

L'aggiunta di un membro dati obbligatorio è una modifica che determina interruzioni, ovvero il tipo più recente può essere ancora inviato agli endpoint con il tipo precedente ma non viceversa. La rimozione di un membro dati contrassegnato come obbligatorio in qualsiasi versione precedente è una modifica che determina interruzioni.

La modifica del valore della proprietà IsRequired da true a false non determina interruzioni ma la modifica di quest'ultima da false a true può determinare interruzioni se eventuali versioni precedenti del tipo non dispongono del membro dati in questione.

Nota

Sebbene la proprietà IsRequired sia impostata su true, i dati in ingresso potrebbero essere Null o zero e per gestire questa possibilità è necessario predisporre un tipo. Non utilizzare IsRequired come meccanismo di sicurezza contro dati in ingresso non validi.

Valori predefiniti omessi

È possibile, sebbene non consigliato, impostare la proprietà EmitDefaultValue nell'attributo DataMemberAttribute su false, come descritto in Valori predefiniti dei membri dati. Se questa impostazione è false, il membro dati non verrà creato se è impostato sul valore predefinito (generalmente Null o zero). L'incompatibilità con i membri dati obbligatori in versioni diverse è dovuta a due motivi:

  • Un contratto dati con un membro dati obbligatorio in una versione non può ricevere dati predefiniti (Null o zero) da una versione diversa nella quale il valore del membro dati EmitDefaultValue è impostato su false.

  • Un membro dati obbligatorio con il valore EmitDefaultValue impostato su false non può essere utilizzato per serializzare il valore predefinito (Null o zero) ma può ricevere tale valore nella deserializzazione. Questo crea un problema di sequenze di andata e ritorno (i dati possono essere letti in ingresso ma non possono essere scritti in uscita). Se, pertanto, IsRequired è true e EmitDefaultValue è false in una versione, la stessa combinazione deve applicarsi a tutte le altre versioni in modo che nessuna versione del contratto dati sia in grado di produrre un valore che non comporti sequenze di andata e ritorno.

Considerazioni sugli schemi

Per una spiegazione sugli schemi prodotti per i tipi di contratto dati, vedere Riferimento allo schema del contratto dati.

Lo schema prodotto da WCF per i tipi di contratto dati non fornisce il controllo delle versioni. Ovvero, lo schema esportato da una certa versione di un tipo contiene solo i membri dati presenti in quella versione. L'implementazione dell'interfaccia IExtensibleDataObject non modifica lo schema per un tipo.

Per impostazione predefinita, i membri dati vengono esportati nello schema come elementi facoltativi, quindi il valore di minOccurs (attributo XML) è impostato su 0. I membri dati obbligatori vengono esportati con minOccurs impostato su 1.

Molte delle modifiche che si ritiene non determinino interruzioni, di fatto le provocano nel caso in cui sia richiesta una stretta osservanza dello schema. Nell'esempio precedente, un'istanza CarV1 con il solo elemento Model eseguirebbe la convalida a fronte dello schema CarV2 che dispone degli elementi Model e Horsepower, entrambi facoltativi. Non è tuttavia vero il contrario, ovvero un'istanza CarV2 non riuscirebbe a eseguire la convalida a fronte dello schema CarV1.

Le sequenze di andata e ritorno comportano inoltre alcune considerazioni aggiuntive. Per altre informazioni, vedere la sezione "Considerazioni sugli schemi" in Contratti dati compatibili con versioni successive.

Altre modifiche consentite

L'implementazione dell'interfaccia IExtensibleDataObject è una modifica che non determina interruzioni. Il supporto delle sequenze di andata e ritorno, tuttavia, non esiste per versioni del tipo precedenti alla versione nella quale IExtensibleDataObject è stato implementato. Per altre informazioni, vedere Contratti di dati compatibili con versioni successive.

Enumerazioni

L'aggiunta o la rimozione di un membro di enumerazione è una modifica che determina interruzioni. La modifica del nome di un membro di enumerazione determina interruzioni, a meno che il nome del contratto non sia identico a quello della versione precedente utilizzando l'attributo EnumMemberAttribute. Per altre informazioni, vedere Tipi di enumerazione nei contratti di dati.

Raccolte

La maggior parte delle modifiche di raccolte non determina interruzioni poiché la maggior parte dei tipi di raccolta sono intercambiabili tra loro nel modello del contratto dati. La trasformazione di una raccolta non personalizzata in una personalizzata e viceversa è, tuttavia, una modifica che determina interruzioni. Inoltre, modificare le impostazioni di personalizzazione della raccolta è una modifica sostanziale che implica il cambiamento del nome del contratto dati e dello spazio dei nomi, del nome dell'elemento ripetuto, del nome dell'elemento key e del nome dell'elemento value. Per altre informazioni sulla personalizzazione della raccolta, vedere Tipi di raccolta in Contratti dati.
Ovviamente, la modifica del contratto dati del contenuto di una raccolta, ad esempio il passaggio da un elenco di numeri interi a un elenco di stringhe, è sostanziale.

Vedi anche