Serializzazione - Seconda parte
a cura di Enrico Barillari (requisiti: conoscenza minima di .Net e C#)

Esempi di attributi XML:

  [XmlElementAttribute("FirstName",typeof(string))]
  public string Nome

  [XmlArray("PhoneNumbers"), XmlArrayItem("PhoneNumber", typeof(string))]
  public string[] Telefoni

Tramite uno dei costruttori della classe XmlSerializer possiamo definire uno spazio dei nomi predefinito per il documento Xml:

XmlSerializer serializer = new XmlSerializer(typeof(Persona), "urn:ebSerialization");

per ottenere:

<?xml version="1.0"?>
<Persona xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:ebSerialization">

Possiamo anche definire un elemento nodo radice personalizzato, passando un secondo parametro al costruttore della classe XmlSerializer:

  XmlSerializer serializer = new XmlSerializer(typeof(Persona),
  new XmlRootAttribute("ebSerialization.Persona"));

Va ricordato che anche l'istanza della classe XmlSerializer necessaria per la deserializzazione deve essere istanziata allo stesso modo: deve esserci corrispondenza tra il serializzatore ed il deserializzatore.
Ecco come risulta modificato l'aspetto del file XML:

<?xml version="1.0" encoding="utf-8"?>
<ebPersona xmlns:xsd="http://www.w3.org/2001/XMLSchema"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns="urn:ebSerialization">
  <FirstName xmlns="ebSerialization">pinco</FirstName>
  <LastName xmlns="ebSerialization">pallino</LastName>
  <Address xmlns="ebSerialization">via tizio caio, 1</Address>
  <City xmlns="ebSerialization">roma</City>
  <PhoneNumbers xmlns="ebSerialization">
    <PhoneNumber>051123456789</PhoneNumber>
    <PhoneNumber>029862147853612</PhoneNumber>
  </PhoneNumbers>
  <BirthDate xmlns="ebSerialization">25/12/2000</BirthDate>
</ebSerialization.Persona>

La classe XmlSerializer espone alcuni eventi, che si possono verificare durante la deserializzazione, che permettono di intercettare eventuali errori dovuti ad elementi non riconosciuti.
Tra questi gli eventi UnknownElement e UnknownAttribute, che si verificano rispettivamente nel caso in cui venga trovato un elemento oppure un attributo non riconosciuti.
L'argomento esposto dall'evento permette di accedere alle proprietà ObjectBeingDeserialized (un riferimento all'oggetto che si sta deserializzando), LineNumber e LinePosition (la riga e la colonna all'interno del documento in cui si trova l'oggetto) e, rispettivamente, Element o Attr (l'elemento o l'attributo che ha provocato l'errore) che a loro volta espongono le proprietà Name e InnerText (nel caso di elemento) o Value (nel caso di attributo).
Per verificare il funzionamento possiamo aggiungere questa riga di codice al metodo Deserialize() della classe ebXmlSerializer:

  serializer.UnknownElement +=new XmlElementEventHandler(serializer_UnknownElement);

ed aggiungere il gestore dell'evento:

  Console.WriteLine("Unknown Element");
  Console.WriteLine("\t" + e.Element.Name + " " + e.Element.InnerXml);
  Console.WriteLine("\t LineNumber: " + e.LineNumber);
  Console.WriteLine("\t LinePosition: " + e.LinePosition);
  Persona x  = (Persona) e.ObjectBeingDeserialized;
  Console.WriteLine (x.Nome);
  Console.WriteLine (sender.ToString());

Ora modifichiamo un elemento nel file XML:

<Phones xmlns="ebSerialization">
  <PhoneNumber>051123456789</PhoneNumber>
  <PhoneNumber>029862147853612</PhoneNumber>
</Phones>

Se mandiamo in esecuzione il metodo deserialize, nella finestra di output ritroveremo:

Unknown Element
  Phones <PhoneNumber xmlns="ebSerialization">051123456789</PhoneNumber>
  <PhoneNumber xmlns="ebSerialization">029862147853612</PhoneNumber>
  LineNumber: 11
  LinePosition: 4
  pinco pallino
System.Xml.Serialization.XmlSerializer

Serializzare una collezione di oggetti
Vediamo ora come serializzare una collezione di oggetti.
Come ho già detto, la classe XmlSerializer accetta un riferimento ad uno specifico tipo, quindi sarà necessario creare istanze specializzate per array di persone, ArrayList, oppure collezioni specializzate.
Possiamo aggiungere alla nostra classe ebXmlSerializer metodi specifici per serializzare array di Persona, oppure ArrayList, modificando il costruttore della classe XmlSerializer per accettare un array di Persona:

  XmlSerializer serializer = new XmlSerializer(typeof(Persona[]));

Oppure un ArrayList composto da istanze di Persona:

  XmlSerializer serializer = new XmlSerializer(typeof(ArrayList),new Type[]{typeof(Persona)});

Proviamo ad eseguire i metodi SerializeArray e DeserializeArray ed otteniamo questo file XML:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfPersona xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Persona>
    <FirstName xmlns="urn:ebSerialization">tizio</FirstName>
    <LastName xmlns="urn:ebSerialization">caio</LastName>
    <Address xmlns="urn:ebSerialization">via sempronio, 1</Address>
    <City xmlns="urn:ebSerialization">roma</City>
    <PhoneNumbers xmlns="urn:ebSerialization">
      <PhoneNumber>051123456789</PhoneNumber>
      <PhoneNumber>029862147853612</PhoneNumber>
    </PhoneNumbers>
    <BirthDate xmlns="urn:ebSerialization">25/12/2000</BirthDate>
  </Persona>
  <Persona>
    <FirstName xmlns="urn:ebSerialization">pinco</FirstName>
    <LastName xmlns="urn:ebSerialization">pallino</LastName>
    <Address xmlns="urn:ebSerialization">via roma</Address>
    <City xmlns="urn:ebSerialization">milano</City>
    <PhoneNumbers xmlns="urn:ebSerialization">
      <PhoneNumber>041225456989</PhoneNumber>
      <PhoneNumber>02862147153612</PhoneNumber>
    </PhoneNumbers>
    <BirthDate xmlns="urn:ebSerialization">01/04/2000</BirthDate>
  </Persona>
</ArrayOfPersona>

Se utilizziamo i metodi SerializeArrayList e DeserializeArrayList otteniamo un file XML simile a questo:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfAnyType xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <anyType xmlns:q1="urn:ebSerialization" xsi:type="q1:Persona">
    <q1:FirstName>tizio</q1:FirstName>
    <q1:LastName>caio</q1:LastName>
    <q1:Address>via sempronio, 1</q1:Address>
    <q1:City>roma</q1:City>
    <q1:PhoneNumbers>
      <q1:PhoneNumber>051123456789</q1:PhoneNumber>
      <q1:PhoneNumber>029862147853612</q1:PhoneNumber>
    </q1:PhoneNumbers>
    <q1:BirthDate>25/12/2000</q1:BirthDate>
  </anyType>
  <anyType xmlns:q2="urn:ebSerialization" xsi:type="q2:Persona">
    <q2:FirstName>pinco</q2:FirstName>
    <q2:LastName>pallino</q2:LastName>
    <q2:Address>via roma, 1</q2:Address>
    <q2:City>milano</q2:City>
    <q2:PhoneNumbers>
      <q2:PhoneNumber>041225456989</q2:PhoneNumber>
      <q2:PhoneNumber>02862147153612</q2:PhoneNumber>
    </q2:PhoneNumbers>
    <q2:BirthDate>01/04/2000</q2:BirthDate>
  </anyType>
</ArrayOfAnyType>

Approfondimento
Abbiamo visto in precedenza come serializzare e deserializzare semplici oggetti, ora facciamo qualche esempio più complesso.
Per iniziare possiamo usare il serializzatore binario per serializzare e deserializzare un'immagine, in un'applicazione Asp.Net.
Per prima cosa ho modificato la classe ebBinaryFormatter, aggiungendo una proprietà che mi permetta di impostare il path completo del file da serializzare e deserializzare:

  ///<summary> Variabile member: nome completo del file</summary>
  private string filePath;

  ///<summary>
  ///Property: Riferimento al path completo del file da serializzare
  ///</summary>
  public string FilePath
  {
    get
    {
      return filePath;
    }
    set
    {
      filePath = value;
    }
  }

Poi ho aggiunto ad una nuova pagina Asp.Net un oggetto INPUT:

"<INPUT id="ImgFile" style="BORDER-RIGHT: 1px solid; BORDER-TOP: 1px solid; Z-INDEX: 101;
LEFT: 8px; BORDER-LEFT: 1px solid; WIDTH: 70%; BORDER-BOTTOM: 1px solid; POSITION: absolute;
TOP: 8px; BACKGROUND-COLOR: beige" type="file" name="ImgFile" runat="server">"

e due pulsanti, che ho associato a questi gestori dell'evento Click:

  using System.IO;
  using System.Drawing.Imaging;
  using ebSerialization;

  private void ButtonSerialize_Click(object sender, System.EventArgs e)
  {
    ebBinarySerializer serializer = new ebBinarySerializer();
    serializer.FilePath = Server.MapPath("Immagine.dat");
    Bitmap img = new Bitmap(ImgFile.PostedFile.InputStream);
    serializer.Serialize(img);
  }

  private void ButtonDeserialize_Click(object sender, System.EventArgs e)
  {
    ebBinarySerializer serializer = new ebBinarySerializer();
    serializer.FilePath = Server.MapPath("Immagine.dat");
    Bitmap img = (Bitmap)serializer.Deserialize();
    Response.ContentType = "image/bmp";
    MemoryStream ms = new MemoryStream();
    img.Save(ms, ImageFormat.Bmp);
    Response.BinaryWrite(ms.GetBuffer());
    ms.Close();
  }

Il primo metodo mi consente di serializzare un file immagine selezionato grazie al controllo Input, in un file nella directory dell'applicazione, in formato binario.
Nel gestore dell'evento collegato al secondo pulsante, prima deserializzo il file binario, poi lo salvo in un flusso in memoria (MemoryStream), infine visualizzo l'immagine sulla pagina.

La stessa cosa si può fare con un Web Service, in modo da archiviare immagini in una cartella nella directory sul server, a prescindere dall'utilizzo in una applicazione Asp.Net o Windows, nell'esempio una cartella denominata ImagesFolder.

Ho creato un Web Service che ho chiamato ImageSerializer con due metodi che utilizzano la classe ebBinarySerializer vista in precedenza:

  private string filePath = @"C:\Inetpub\wwwroot\Images\ImagesFolder\";

  [WebMethod(Description="Restituisce un'immagine in formato binario")]
  public object GetImage(string fileName)
  {
    ebBinarySerializer serializer = new ebBinarySerializer();
    serializer.FilePath = filePath + fileName;
    return serializer.Deserialize();
  }
  [WebMethod(Description="Serializza un'immagine in formato binario")]
  public void SetImage(byte[] image, string fileName)
  {
    ebBinarySerializer serializer = new ebBinarySerializer();
    serializer.FilePath = filePath + fileName;
    serializer.Serialize(image);
  }

Poi ho aggiunto una Web Reference ad una applicazione Asp.Net, ed ho utilizzato la stessa pagina Asp.Net dell'esempio precedente, alla quale ho aggiunto solo una TextBox nella quale inserire il nome del file immagine da deserializzare, ed ho collegato i controlli ButtonSerialize e ButtonDeserialize a questi metodi:

  private void ButtonSerialize_Click(object sender, System.EventArgs e)
  {
    localhost.ImageSerializer serializer = new SerializeImages.localhost.ImageSerializer();
    Bitmap img = new Bitmap(ImgFile.PostedFile.InputStream);
    MemoryStream ms = new MemoryStream();
    img.Save(ms, ImageFormat.Bmp);
    byte[] bytes = new Byte[ms.Length];
    bytes = ms.GetBuffer();
    ms.Close();
    string fileName =
		       ImgFile.PostedFile.FileName.Substring(ImgFile.PostedFile.FileName.LastIndexOf(@"\") + 1);
    serializer.SetImage(bytes, fileName);
  }

  private void ButtonDeserialize_Click(object sender, System.EventArgs e)
  {
    localhost.ImageSerializer serializer = new SerializeImages.localhost.ImageSerializer();
    Response.ContentType = "image/bmp";
    byte[] bytes = (byte[])serializer.GetImage(this.TextBoxImage.Text);
    Response.OutputStream.Write(bytes, 0, bytes.Length);
  }

Per prima cosa ho creato una istanza del Web Service, poi ho trasformato l'immagine in un array di byte, utilizzando ad un flusso in memoria, dal momento che la classe Bitmap non possiede un costruttore predefinito senza parametri, per cui non è serializzabile.

(parte precedente)