Serializzazione – Introduzione
Prima della pratica un po' di teoria non guasta mai. Serializzazione significa conversione/trasformazione, infatti un oggetto, con le sue proprietà e metodi, viene convertito in un flusso XML o in Binario per poter essere archiviato (su un media esempio su disco) o trasportato (su una rete/network). Visto che stiamo parlando di servizi consumabili via rete la serializzazione che più si adatta al modello in questione è quella
XML, un flusso standard ed elaborabile da qualsiasi applicazione, indipendentemente dalla piattaforma; Ad esempio possiamo serializzare un oggetto e trasportarlo da client a server per mezzo del protocollo
HTTP. La classe Principale è la
XmlSerializer di cui i metodi più importanti sono:
-
Serialize (che trasforma il nostro oggetto in XML)
-
Deserialize (che riconverte l'XML in 'oggetto)
N.B. Iniziamo subito a fornire alcuni link della guida in linea dove trovate alcuni nomi di classi che utilizzeremo in questo articolo. Per ciò che riguarda la serializzazione i namespaces di riferimento con relative sottoclassi sono i seguenti:
-
System.Runtime.Serialization -
System.Xml.Serialization Non tutti gli oggetti possono essere serializzati in formato
XML, in questi casi se si vuole comunque trasportare l'oggetto è necessario utilizzare la serializzazione binaria, perdendo in compatibilità tra sistemi.Per controllare la serializzazione di un oggetto si utilizzano degli attributi, ad esempio:
[Serialize]
public class Utente
{
[XmlElement]
public string Nome{}
}
In questo caso abbiamo dichiarato la classe
Utente serializzabile, e la proprietà
Nome come un elemento del nostro XML. Se serializzassimo questo oggetto la rappresentazione XML sarebbe simile alla seguente:
<Utente>
<Nome>Nome utente</Nome>
</Utente>
possiamo anche cambiare il nome del nostro elemento secondo le nostre esigenze, utilizzando l'attributo
ElementName per
XmlElement, riprendendo l'esempio di prima:
[Serialize]
public class Utente
{
[XmlElement(ElementName=“Name”)]
public string Nome{}
}
ed avrà un output di questo tipo:
<Utente>
<Name>Nome utente</Name>
</Utente>
Come avrete notato l'elemento/nodo
Nome si è trasformato in
Name, questo ci da la possibilità di creare un livello ulteriore di astrazione rispetto alla nostra classe.
Serializable
[Serializable] è il più utilizzato e conosciuto dei metodi di serializzazione, infatti basta associare alla nostra classe l'attributo, permettendoci di trasformarla in
XML o in Binary, ecco un esempio:
[Serializable]
public class Persona
{
...
public string Nome
{
get { ... }
set { ... }
}
...
}
Particolarità dell'attributo
[Serializable] è che ogni elemento, sia pubblico che privato, viene serializzato. Se vogliamo ottenere il contrario dobbiamo utilizzare l'attributo
[NonSerialized]. Utilizzando questo metodo abbiamo la possibilità di utilizzare la nostra classe serializzata anche per altri scopi, ad esempio per serializzare in binario la nostra classe, oltre a distribuirla attraverso il WCF, con la classe
BinaryFormatter.
XMLSerialization
La serializzazione
XML non è una novità del
.NET Framework 3.5, infatti era già utilizzata nelle versioni precedenti nei Web Services ASP.NET, tutto questo per semplificare il lavoro a chi ha sviluppato molti web service e vuole implementarli in WCF. Ecco la nostra classe
Persona con gli attributi per l'
XMLSerialize:
[XmlRoot("Persona",Namespace="urn:Persona-com")]
public class Persona
{
...
[XmlAttribute("Nome")]
public string Name
{
get { ... }
set { .. }
}
...
}
Come si può notare la classe che desidero serializzare in
XML ha assegnato l'attributo
[XmlRoot] che identifica la root del nostro XML, e ad ogni elemento che voglio rendere disponibile nel mio file XML viene impostato l'attributo
[XmlAttribute].
WCF automaticamente provvederà a serializzare e deserializzare la nostra classe, però visto che la serializzazione di default è quella del
DataContract, è necessario specificare nel nostro messaggio il tipo di engine per la deserializzazione, ecco perché nel nostro
ServiceContract specifichiamo l'attributo
[XmlSerializerFormat], in questo modo:
[ServiceContract]
[XmlSerializerFormat]
interface PeopleService
{
...
}
A questo punto il nostro client può creare una referenza al servizio, creando una
ServiceReference nel nostro progetto client e stanziare un oggetto
Proxy per interrogare i metodi del nostro
ServiceContract, ad esempio:
//csharpHostService è il nome della referenza al servizio
csharpHostService.PeopleServiceClient client = new csharpClient.csharpHostService.PeopleServiceClient();
//Metodo che inserisce una nuova persona
client.AddPerson("Pippo", "Pluto", "Indirizzo");
//Oggetto serializzato Persona
csharpHostService.Persona _person = client.GetPerson("Pippo");
Console.WriteLine("Utente {0} inserito", _person.Nome);
DataContract
La dichiarazione del
DataContract altro non è che una forma di serializzazione ad hoc di
WCF, infatti utilizza un engine chiamato
"Data Contract Serializer" che si preoccupa della serializzazione e deserializzazione degli oggetti. Ecco come è definito un
DataContract:
[DataContract]
public class Person
{
...
[DataMember]
public string Name
{
get { ... }
set { ...}
}
...
}
Come potete notare alla classe viene assegnato l'attributo
[DataContract] che la identifica come serializzabile, mentre ad ogni membro della classe che vogliamo rendere pubblico attraverso il servizio deve essere assegnato l'attributo
[DataMember]. Con questi due semplici attributi il nostro
Custom Type è pronto, ma le classi
DataContractAttribute e
DataMemberAttribute ci offrono una serie di opzioni per customizzare ancora di più il processo di serializzazione.
DataContractAttribute-
Name: Nome della classe serializzata, questo ci permette di impostare il nome che desideriamo per la classe, per esempio rendere pubblica la classe Person sotto falso nome, ad esempio Persona.
-
Namespace: Nome del namespace che verrà utilizzato nell'xml serializzato nel caso in cui abbiamo esigenze particolari in integrazione di applicazioni.
DataMemberAttribute-
Name: Nome del membro della classe, come per il DataContract possiamo definire un nome diverso da quello della proprietà, ad esempio da Name e Nome
-
Order: Definisce l'ordine in cui i membri vengono serializzati.
-
IsRequired: Indica se il membro deve essere presente obbligatoriamente alla deserializzazione dell'oggetto
-
EmitDefaultValue: Omette la scrittura di un membro se il suo valore equivale a quello di default.
Con queste proprietà il nostro codice lo possiamo trasformare in:
[DataContract(Name="Persona", Namespace="urn:persona-com")]
public class Person
{
...
[DataMember(Name="Nome",Order=2)]
public string Name
{
get { ... }
set { ... }
}
...
}
In questo caso il client potrà utilizzare come tipo di dati una classe
Persona che avrà tra le sue proprietà una denominata
Nome, ad esempio:
//csharpHostService è il nome della referenza del servizio
csharpHostService.Persona _person = new csharpHostService.Persona();
_person.Nome = “pippo”;
Console.WriteLine(_person.Nome);
Di seguito una serie di tipi che sono supportati dal
DataContract- Tipi di dati del CLR
- Array di Byte, DateTime, TimeSpan, GUID, Uri, XmlQualifiedName, XmlElement e Array XmlNode
- Enum
- Tipi con attributo DataContract o attributo CollectionDataContract
- Tipi di dati che implementano l'interfaccia IXmlSerializable
- Array e Collection, incluse List
, Dictionary e Hashtable
- Tipi con l'attributo Serializable e inclusi quelli che implementano l'interfaccia ISerializable.
NetDataContractSerializer
Piccolo accenno a questo metodo di serializzazione. Questo engine supporta gli attributi [Serializable] e [DataContract], con tutti gli annessi e connessi a questi due engine. Al contrario degli due metodi invece non è necessario specificare degli attributi sulla nostra classe per permetterne la serializzazione, questo però comporta che funziona solo nel caso in cui il client sia Microsoft (era intuibile dal nome).
Visto che non dobbiamo modificare il DataContract, l'unica parte di codice che ci interessa risiede nel ServiceContract, aggiungendo l'attributo [NetDataContract] ai metodi che vogliamo esporre.
[ServiceContract]
interface PeopleService
{
...
[OperationContract]
[NetDataContract]
Person GetPerson(string name);
...
}
Da notare il fatto che Microsoft non incoraggia l'utilizzo di questo tipo di serializzazione, infatti per poterla abilitare è necessario specificare un Behavior che ne permetta l'utilizzo.
Generazione da Schema XSD
Visto che a Microsoft stanno a cuore le nostre ore lavorative, ha pensato di fornirci un tool per la creazione automatica dei DataContract partendo da uno schema XSD, il tool in questione si chiama svcutil.exe. Questa applicazione ha un infinità di opzioni (basta guardare l'help) ma quella che a noi interessa è lo switch /dconly. Prendiamo ad esempio un semplice schema
<?xml version=”1.0” encoding=”utf-8” ?>
<xs:schema id=”XMLSchema1”>
<xs:complexType name=”Person”>
<xs:sequence>
<xs:element name=”Nome” type=”xs:string” />
<xs:element name=”Cognome” type=”xs:string” />
</xs:sequence>
</xs:complexType>
<xs:element name=”Persona” type=”mstns:Person” />
</xs:schema>
In questo caso abbiamo definito un tipo di dati Person che ha due proprietà, Nome e Cognome, e abbiamo creato un elemento Persona di tipo Person.
A questo punto eseguiamo il nostro tool eseguendo da dos il comando
svcutil.exe [path schema XSD] /dconly [/language:C#] o [/language:VB]
Automaticamente il nostro tool ci genererà un file .cs con al suo interno il nostro DataContract e i DataMember, file .cs che possiamo includere senza problemi nel nostro progetto.
Conclusioni
In questo articolo abbiamo esplorato le varie possibilità di serializzazione attraverso il Windows Communication Foundation, tra le più importanti XmlSerializer e DataContract. Il Framework ci mette a disposizione anche in questo caso diversi strumenti utili, per poter sviluppare nuove applicazioni e/o integrarne di vecchie con grande facilità e minori sprechi di tempo.