Technology Experience

Contenuti gestiti da Igor Damiani
posts - 949, comments - 2741, trackbacks - 15120

My Links

News

  • Questo blog si propone di raccogliere riflessioni, teoriche e pratiche, su tutto quello che riguarda il world-computing che mi sta attorno: programmazione in .NET, software attuale e futuro, notizie provenienti dal web, tecnologia in generale, open-source.

    L'idea è quella di lasciare una sorta di patrimonio personale, una raccolta di idee che un giorno potrebbe farmi sorridere, al pensiero di dov'ero e cosa stavo facendo.

    10/05/2005,
    Milano

Archives

Post Categories

Generale

[70-536, #12] Serializzazione di una classe con XmlSerializer e BinaryFormatter

Introduzione
La serializzazione è un processo tramite il quale trasformiamo l'istanza di una classe in un formato trasportabile. Il formato può essere uno stream di tipo XML o di tipo binario, dipendentemente dal tipo di serializzazione che intendiamo ottenere. Il formato XML ha diversi vantaggi: innanzitutto è human-readable, quindi può essere facilemente essere letto con un qualsiasi editor di testi (più o meno specializzato per il trattamento di documenti XML). In secondo luogo, uno stream XML può facilmente essere trasmesso ad un web-services via http, dato che non contiene caratteri speciali che potrebbero essere filtrati o bloccati da regole impostate in un eventuale firewall.

Il formato binario invece, come dice il nome stesso, serializza l'istanza di classe in un più generico stream di bytes. E' un formato più compatto rispetto alla serializzazione XML. Se decidiamo di serializzare in formato binario abbiamo un grande vantaggio: possiamo intercettare la richiesta di serializzazione della classe ed intervenire, customizzando completamente il modo con cui il FX serializzerebbe di default. D'altro canto, il formato binario per sua natura potrebbe contenere caratteri anomali(passatemi il termine) che potrebbe venir bloccato da eventuali firewall: questo lo rende poco adatto alla trasmissione http.

Serializzazione in formato XML
La serializzazione più semplice in assoluto passa attraverso la classe XmlSerializer del FX. Nel codice che ho messo a disposizione c'è già una classe XMLSerialization con tutta una serie di metodi statici che serializzano le classi coinvolte dal progetto (Storage, Shelf, Book e Chapter): vi rimando a quel codice per vedere ed esaminare concretamente il funzionamento. La cosa importante da sapere è che ogni classe .NET serializza soltanto le proprietà pubbliche di cui dispone. Se va bene il comportamento di default che ci offre il FX, possiamo portare a termine la serializzazione XML in modo molto semplice in questo modo:

static public void Serialize(Chapter cp, string fileName)
{
    
if (regole == null)
        SetupWriterSettings();

    XmlWriter wri = XmlWriter.Create(fileName, regole);
    XmlSerializer ser = 
new XmlSerializer(typeof(Chapter));
    ser.Serialize(wri, cp);
    wri.Close();
}

La classe XmlWriter del FX ci permette di ottenere un oggetto con il quale gestire uno stream XML: nell'esempio qui sopra, lo usiamo per scrivere fisicamente un file su disco. La chiamata a SetupWriterSettings() inizializza un'istanza della classe XmlWriterSettings, un oggetto che uso spesso per definire alcune proprietà relative al modo con cui il FX genererà lo stream (indentazione, encoding, creare una nuova linea sugli attributi, etc.). Il codice che inizializza il mio oggetto regole è questo:

private static void SetupWriterSettings()
{
    regole = 
new XmlWriterSettings();
    regole.Indent = 
true;
    regole.IndentChars = "    ";
    regole.ConformanceLevel = ConformanceLevel.Auto;
    regole.NewLineOnAttributes = 
false;
    regole.Encoding = System.Text.Encoding.Unicode;
}

Successivamente, istanzio un XmlSerializer, indicando nel suo costruttore la classe che sto serializzando. Il processo vero e proprio viene portato a termine dalla linea:

ser.Serialize(wri, cp);

Questa riga serializza l'oggetto cp (Chapter) nello stream indicato da wri (XmlWriter). Nulla di più, nulla di meno. Ovviamente, a questo punto possiamo andare a dare un'occhiata al file generato, aprirlo con un editor qualsiasi e dare un'occhiata a quanto generato. Ad esempio, un'istanza di classe Chapter viene resa nel seguente formato XML:

<?xml version="1.0" encoding="utf-16"?>
<Chapter
    
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <
Title>Prologo</Title>
<
/Chapter>

Ovviamente, oggetti più complessi creano un flusso XML più complesso. Ecco un esempio dello stream XML generato da un'istanza della classe Book.

<?xml version="1.0" encoding="utf-16"?>
<Book
    
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <
Title>La Spada di Shannara</Title>
    <Pages>
0</Pages>
    <InLoan>
false</InLoan>
    <Bookmark>
0</Bookmark>
    <Chapters>
        <Chapter>
            <
Title>Prologo</Title>
        <
/Chapter>
        <Chapter>
            <
Title>Inizio</Title>
        <
/Chapter>
    <
/Chapters>
<
/Book>

Nel suo comportamento di default, il FX serializza tutte le public properties della classe. Tramite una serie di attributi possiamo decorare ogni property per modificare questo comportamento. Ad esempio, possiamo stabilire che la property Author della classe Book venga serializzata con il tag <Autore> (XmlElementAttribute). Oppure, ancora, possiamo dire di ignorare una certa property con l'attributo XmlIgnoreAttribute. Vi consiglio una lettura veloce al documento "Attributes That Control XML Serialization" per maggiori dettagli sugli attributi disponibili nel FX a questo scopo.

Serializzazione binaria
La serializzazione binaria viene effettuata tramite l'utilizzo della classe BinaryFormatter nel namespace System.Runtime.Serialization.Formatters.Binary (lo preciso perchè ci ho messo una vita prima di capire dove trovarla - viva l'Object Browser, alias CTRL W + J ).

static public void SerializeBinary(Chapter cp, string fileName)
{
    FileStream fs = 
new FileStream(fileName, FileMode.CreateNew);
    BinaryFormatter form = 
new BinaryFormatter();
    form.Serialize(fs, cp);
    fs.Close();
    fs.Dispose();
}

Questa volta lo stream fs è creato attraverso la classe FileStream, mentre la classe BinaryFormatter compie realmente l'operazione di serializzazione dell'oggetto cp. Il file generato non è human-readable, è più compatto del corrispondente file XML che abbiamo visto prima. Anche in questo caso, però, contiene a tutti gli effetti tutte le informazioni necessarie al FX per deserializzare, e quindi per ricostruire l'istanza dell'oggetto in memoria.

Implementare l'interfaccia ISerializable
Nei processi di serializzazione mostrati qui sopra, non abbiamo alcuna possibilità di intervenire sul processo stesso. Il FX ci viene in aiuto, ha lui il compito di serializzare e a noi va bene così, soprattutto nella maggior parte dei casi. Se per qualsiasi motivo vogliamo modificare questo comportamento, possiamo utilizzare l'interfaccia ISerializable. Questa interfaccia richiede l'implementazione di un solo metodo GetObjectData(), che viene chiamata automaticamente dal FX quando viene richiesta la serializzazione della classe stessa. Vediamo nel dettaglio come funziona questa cosa.

using System;

[Serializable]
public class Chapter : ISerializable
{
    
// codice, codice, codice
    
    
public void GetObjectData(SerializationInfo info, StreamingContext context)
    {

    }
}

Notare che:

  1. ho dovuto decorare l'intera classe con l'attributo Serializable (namespace System).
  2. ho dovuto ovviamente indicare che Chapter implementa ISerializable
  3. in conseguenza al punto (2), ho dovuto implementare GetObjectData

Il metodo GetObjectData() viene chiamata al momento della serializzazione binaria della classe (non viene usato se serializziamo con XML). In questo modo agiamo sull'oggetto info (classe SerializationInfo) aggiungendo tutte le informazioni che intendiamo serializzare sullo stream tramite la chiamata al metodo AddValue (il quale dispone di 16 overload diversi per ogni value-type)

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
    info.AddValue("Title", _Title);
    info.AddValue("CurrentDate", DateTime.Now.ToShortDateString());

    
string user = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
    info.AddValue("CurrentUser", user);
}

Nel codice qui sopra, ad esempio, oltre a serializzare la property Title (che avrebbe fatto anche il FX di default), aggiungiamo la data corrente e l'utente Windows corrente che sta compiendo questa operazione. Tutto quello che andiamo ad aggiungere ad info verrà a tutti gli effetti serializzato sullo stream.

powered by IMHO 1.2

Print | posted on mercoledì 8 febbraio 2006 14:07 | Filed Under [ Esame 70-536 ]

Feedback

Gravatar

# re: [70-536, #12] Serializzazione di una classe con XmlSerializer e BinaryFormatter

Complimenti Igor, è interessante quello che hai scritto.
Solo due cose: non credo che i firewall solitamente si occupino di analizzare il contenuto dei pacchetti, molto più spesso si limitano a permettere o negare le comunicazioni su particolari protocolli e/o porte. Anche perchè per un eventuale controlli sui caratteri dovrebbero anche occuparsi di effettuare una decodifica...
Quello che succede è che normalmente si spediscono gli stream binari su TCP (di solito chiuso dai firewall) e gli stream XML su HTTP (normalmente aperti sui firewall).
Per quanto riguarda il controllo sulla serializzazione nel framework esiste anche l'interfaccia IXmlSerializable (in System.Xml.Serialization). A dire il vero in 1.1 è documentata come "The IXmlSerializable type supports the .NET Framework infrastructure and is not intended to be used directly from your code". Ma in 2.0 è rimasta identica ed è normalmente documentata, cioè tranquillamente utilizzabile.

ciao
m.
08/02/2006 17:14 | Matteo
Gravatar

# re: [70-536, #12] Serializzazione di una classe con XmlSerializer e BinaryFormatter

un grazie a Michele Bersani che mi ha trovato alcuni bachi nel testo: alcuni dei links puntavano a pagina di ricerca su msdn2.microsoft.com, adesso dovrebbe essere tutto ok (spero!).

grazie anche a Matteo per le doverose precisazioni sulla security via http, serializzazione binaria o XML. Bravo, grazie, a risentirci!!! :-)
08/02/2006 17:47 | Igor Damiani
Gravatar

# [70-536, #13] Deserializzazione di una classe da uno stream XML o binario (con contorno di Generics)

09/02/2006 17:20 | Technology Experience
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET