Introduzione Proseguo la presentazione degli helper inseriti nel progetto DnwBaseCs (DnwBaseVb), parlando di una classe dedicata ai servizi di serializzazione XML. La classe XmlSerializer Questa classe è l'evoluzione di una vecchia classe che è stata spiegata dal mio amico e collega Enrico Barillari sviluppata con Visual Studio 2003, lo scopo della classe è fornire servizi automatici per serializzare classi di tipo Entity ed Entity Collection su files XML. Una classe Entity, per chi se lo chiedesse, è semplicemente una classe disegnata per conservare dati e fornire dati ad una applicazione, questo è il motivo primario per cui è buona cosa che sia dotata di un modo per essere serializzata. Non sapete cosa significa serializzare? Vi capisco, dalla prima volta che ho sentito la parola a quando ho capito cosa volesse dire è passato un bel po' di tempo. Direi che la definizione più semplice sia Salvataggio dello stato di un oggetto su supporto non volatile. Ovvero, serializzare un oggetto significa salvare il suo stato su disco o altro supporto fisico, per poter poi ricreare un oggetto identico nello stesso stato ripristinando i dati salvati. using Dnw.Base.Logger;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace Dnw.Base.Xml
{
public static class ObjSerializer
{
private readonly static string mClassName =
System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name;
}
} Imports Dnw.Base.Logger
Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Text
Imports System.Xml
Imports System.Xml.Serialization
Namespace Xml
Public NotInheritable Class ObjSerializer
Private Sub New()
End SubPrivate Shared ReadOnly mClassName As String _
= System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name
End ClassEnd NamespaceLa struttura base della classe è come per molti degli helper una classe statica è identica alla struttura della classe precedente se non per il fatto che ospita i namespace dedicati a File System (IO) Serializzazione Xml e dati Xml. Il metodo BuildReader public static XmlTextReader BuildReader(string pXmlString)
{
NameTable nt = new NameTable();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nt);
nsmgr.AddNamespace("bk", "urn:sample");
XmlParserContext context = new XmlParserContext(null, nsmgr, null, XmlSpace.None);
return (new XmlTextReader(pXmlString, XmlNodeType.Element, context));
} Public Shared Function BuildReader(ByVal pXmlString As String) As XmlTextReader
Dim nt As New NameTable()
Dim nsmgr As New XmlNamespaceManager(nt)
nsmgr.AddNamespace("bk", "urn:sample")
Dim context As New XmlParserContext(Nothing, nsmgr, Nothing, XmlSpace.None)
Return (New XmlTextReader(pXmlString, XmlNodeType.Element, context))
End FunctionQuesto metodo genera un XmlTextReader per poter leggere una stringa contenente una classe serializzata come si leggerebbe un file. La vostra domanda ovviamente sarà: Perché serializzare una classe su una stringa se hai appena detto che la serializzazione serve a salvare su memoria non volatile. Ebbene, se la memoria non volatile in cui memorizzare lo stato di un oggetto è il campo di un database, la serializzazione va fatta su una stringa. E la deserializzazione ovviamente implica trasformare la stringa in un oggetto deserializzabile. L'XmlTextReader è una classe che permette di Leggere un file XML interpretandone la struttura, vi invito a leggere di più sulla classe su MSDN. Per generarlo, nel nostro metodo abbiamo bisogno di tre altri oggetti, un NameTable, con cui generare un XmlNamespaceManager. Il NameTable è una classe che il text reader utilizza internamente per memorizzare un unica volta i nomi ricorsivi degli elementi di un file XML e per compiere comparazioni molto più rapide delle comparazioni fra stringhe. Anche su NameTable potete sapere di più con un giro su MSDN. Il Namespace Manager è un oggetto usato internamente dal reader come il NameTable, il suo scopo è permettere la risoluzione dei Namespace contenuti all'interno del file XML (risoluzione è un'altra delle traduzioni letterali che non hanno nulla a che vedere con il significato del Devoto Oli per la parola, perciò occhio.), MSDN vi può dare più informazioni in merito anche in questo caso. Nel nostro caso, per poter generare il TextReader dalla stringa, generiamo un Namespace fittizio all'interno del namespace manager per poter ottenere il terzo oggetto di cui abbiamo bisogno per poter generare il reader. Questo terzo oggetto è l'XmlParserContext, un oggetto che fornisce al text reader tutte le informazioni che gli sono necessarie per processare un frammento di XML. Vi rimando, al solito, a MSDN per saperne di più. Dopo aver predisposto il necessario, terminiamo il nostro metodo generando il Text Reader al cui costruttore passiamo la stringa contenente l'oggetto serializzato, il tipo di nodi che andremo a leggere, ed il contesto che abbiamo appena generato. Il metodo DeserializeFromFile public static object DeserializeFromFile(Type pTypeToDeserialize, string pPath)
{
XmlTextReader reader = null;
object ret = null;
try
{
// Creo un flusso per l'operazione di lettura dei dati dal file
reader = new XmlTextReader(pPath);
// Occorre un'istanza della classe XmlSerializer
XmlSerializer serializer = new XmlSerializer(pTypeToDeserialize);
// e questo é tutto ciò che serve per leggere i dati dal file formato XML
ret = serializer.Deserialize(reader);
}
catch (Exception ex)
{
throw new ApplicationException(" " + mClassName + "."
+ System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
}
finally
{
if (reader != null)
{
reader.Close();
}
}
return (ret);
} Public Shared Function DeserializeFromFile(ByVal pTypeToDeserialize As Type, _
ByVal pPath As String) As ObjectDim reader As XmlTextReader = NothingDim ret As Object = NothingTry' Creo un flusso per l'operazione di lettura dei dati dal file
reader = New XmlTextReader(pPath)
' Occorre un'istanza della classe XmlSerializer Dim serializer As New XmlSerializer(pTypeToDeserialize)
' e questo é tutto ciò che serve per leggere i dati dal file formato XML
ret = serializer.Deserialize(reader)
Catch ex As Exception
Throw New ApplicationException(" " + mClassName + "." + _
System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex)
FinallyIf reader IsNot Nothing Then
reader.Close()
End IfEnd TryReturn (ret)
End FunctionIl nostro secondo metodo, ci serve per leggere un file XML contenente un oggetto e generare l'oggetto. Per poterlo fare, ci serve sapere di che tipo di oggetto si tratta, pertanto fra i nostri parametri c'è il Type dell'oggetto e visto che leggeremo un file su disco, il path completo del file. Per prima cosa, generiamo l'oggetto necessario a leggere il nostro file, quindi, utilizziamo un altro dei costruttori della classe XmlTextReader per generare il reader dal file dei dati. Fatto questo, generiamo un XmlSerializer, classe del framework dedicata a Serializzare e Deserializzare oggetti, anche in questo caso, per saperne di più MSDN è il posto giusto. Infine, concludiamo il tutto Deserializzando il file con la semplice chiamata al metodo apposito del nostro XmlSerializer. Visto che vogliamo utilizzare la serializzazione non solo su oggetti semplici, ma anche su oggetti che possono essere più complessi, come ad esempio le collezioni, predisponiamo una versione della deserializzazione ove possiamo specificare una lista di oggetti che comporranno la nostra classe. public static object DeserializeFromFile(Type pTypeToDeserialize, Type[] pExtraTypes, string pPath)
{
XmlTextReader reader = null;
object ret = null;
try
{
// Creo un flusso per l'operazione di lettura dei dati dal file
reader = new XmlTextReader(pPath);
// Occorre un'istanza della classe XmlSerializer
XmlSerializer serializer = new XmlSerializer(pTypeToDeserialize, pExtraTypes);
// e questo é tutto ciò che serve per leggere i dati dal formato XML
ret = serializer.Deserialize(reader);
}
catch (Exception ex)
{
throw new ApplicationException(" " + mClassName + "."
+ System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
}
finally
{
if (reader != null)
{
reader.Close();
}
}
return (ret);
} Public Shared Function DeserializeFromFile(ByVal pTypeToDeserialize As Type, _
ByVal pExtraTypes As Type(), ByVal pPath As String) As ObjectDim reader As XmlTextReader = NothingDim ret As Object = NothingTry' Creo un flusso per l'operazione di lettura dei dati dal file
reader = New XmlTextReader(pPath)
' Occorre un'istanza della classe XmlSerializer Dim serializer As New XmlSerializer(pTypeToDeserialize, pExtraTypes)
' e questo é tutto ciò che serve per leggere i dati dal formato XML
ret = serializer.Deserialize(reader)
Catch ex As Exception
Throw New ApplicationException(" " + mClassName + "." + _
System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex)
FinallyIf reader IsNot Nothing Then
reader.Close()
End IfEnd TryReturn (ret)
End FunctionIn questo caso, oltre al tipo principale, ovvero il tipo della classe da ottenere, passiamo al metodo un array contenente i tipi contenuti nella classe, in modo che il serializer conosca esattamente la lista degli oggetti che può trovare nel nostro file XML. Per quel che riguarda il codice, non c'è nulla di diverso dal metodo precedente, salvo i parametri passati al costruttore del Serializzatore. Il metodo DeserializeFromString public static object DeserializeFromString(Type pTypeToDeserialize, string pXmlString)
{
object ret = null;
try
{
XmlReader xr = BuildReader(pXmlString);
XmlSerializer serializer = new XmlSerializer(pTypeToDeserialize);
ret = serializer.Deserialize(xr);
xr.Close();
}
catch (Exception ex)
{
throw new ApplicationException(" " + mClassName + "."
+ System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
}
return (ret);
} Public Shared Function DeserializeFromString(ByVal pTypeToDeserialize As Type, _
ByVal pXmlString As String) As ObjectDim ret As Object = NothingTryDim xr As XmlReader = BuildReader(pXmlString)
Dim serializer As New XmlSerializer(pTypeToDeserialize)
ret = serializer.Deserialize(xr)
xr.Close()
Catch ex As Exception
Throw New ApplicationException(" " _
+ mClassName + "." + System.Reflection.MethodBase.GetCurrentMethod().Name _
+ ": " + ex.Message, ex)
End TryReturn (ret)
End FunctionQuesto metodo permette di deserializzare un oggetto XML serializzato su una stringa, quindi come abbiamo detto ad esempio qualcosa che abbiamo memorizzato su un database. I Parametri passati come vediamo sono uguali a quelli del metodo precedente, solo che la stringa invece del nome di un file contiene i nostri dati XML. Il metodo per prima cosa genera l'XmlReader utilizzando il primo metodo spiegato in questo articolo, poi esegue le stesse due operazioni del metodo precedente e genera l'oggetto dalla stringa di serializzazione. Anche per questo metodo, aggiungiamo la versione dedicata alle classi complesse. public static object DeserializeFromString(Type pTypeToDeserialize,
Type[] pExtraTypes, string pXmlString)
{
object ret = null;
try
{
XmlReader xr = BuildReader(pXmlString);
XmlSerializer serializer = new XmlSerializer(pTypeToDeserialize, pExtraTypes);
ret = serializer.Deserialize(xr);
xr.Close();
}
catch (Exception ex)
{
throw new ApplicationException(" " + mClassName + "."
+ System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
}
return (ret);
} Public Shared Function DeserializeFromString(ByVal pTypeToDeserialize _
As Type, ByVal pExtraTypes As Type(), _
ByVal pXmlString As String) As ObjectDim ret As Object = NothingTryDim xr As XmlReader = BuildReader(pXmlString)
Dim serializer As New XmlSerializer(pTypeToDeserialize, pExtraTypes)
ret = serializer.Deserialize(xr)
xr.Close()
Catch ex As Exception
Throw New ApplicationException(" " + mClassName + "." _
+ System.Reflection.MethodBase.GetCurrentMethod().Name _
+ ": " + ex.Message, ex)
End TryReturn (ret)
End FunctionLa differenza, come in precedenza, sta solo nel parametro con la lista dei tipi secondari di dati. Il metodo SerializeToFile public static void SerializeToFile(string pPath, object pObjToSerialize, Type pTypeToSerialize)
{
XmlTextWriter writer = null;
try
{
// Creo un flusso per l'operazione di scrittura dei dati su file
writer = new XmlTextWriter(pPath, Encoding.UTF8);
// write a readable file
writer.Formatting = Formatting.Indented;
writer.Indentation = 4;
// Occorre un'istanza della classe XmlSerializer
XmlSerializer serializer = new XmlSerializer(pTypeToSerialize);
// e questo é tutto ciò che serve per persistere i dati
serializer.Serialize(writer, pObjToSerialize);
}
catch (Exception ex)
{
EventLogger.SendMsg(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex);
throw new ApplicationException(" " + mClassName + "."
+ System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
}
finally
{
// Chiudere sempre i flussi non appena possibileif (writer != null)
{
writer.Close();
}
}
} Public Shared Sub SerializeToFile(ByVal pPath As String, ByVal pObjToSerialize _
As Object, ByVal pTypeToSerialize As Type)
Dim writer As XmlTextWriter = NothingTry' Creo un flusso per l'operazione di scrittura dei dati su file
writer = New XmlTextWriter(pPath, Encoding.UTF8)
' write a readable file
writer.Formatting = Formatting.Indented
writer.Indentation = 4
' Occorre un'istanza della classe XmlSerializer Dim serializer As New XmlSerializer(pTypeToSerialize)
' e questo é tutto ciò che serve per persistere i dati
serializer.Serialize(writer, pObjToSerialize)
Catch ex As Exception
EventLogger.SendMsg(mClassName, System.Reflection.MethodBase.GetCurrentMethod(), ex)
Throw New ApplicationException(" " + mClassName + "." _
+ System.Reflection.MethodBase.GetCurrentMethod().Name, ex)
Finally' Chiudere sempre i flussi non appena possibile If writer IsNot Nothing Then
writer.Close()
End IfEnd TryEnd SubCon questo metodo, salviamo su file lo stato di una classe, per farlo, utilizziamo un XmlTextWriter, la classe con le funzioni opposte all'XmlTextreader, anche per le sue funzionalità vi rimando ad MSDN. Nel nostro metodo, prima di iniziare a scrivere il file, predisponiamo la formattazione del file XML in modo che risulti leggibile aggiungendo le indentazioni automatiche da 4 caratteri per ogni livello del file XML.
Generiamo quindi un XmlSerializer per il tipo di dato che stiamo serializzando. Scriviamo il file usando il serializer e chiudiamo il Writer. Proseguiamo con la versione del metodo predisposta per le classi con oggetti complessi. public static void SerializeToFile(string pPath, object
pObjToSerialize, Type pTypeToSerialize, Type[] pExtraTypes)
{
XmlTextWriter writer = null;
try
{
// Creo un flusso per l'operazione di scrittura dei dati su file
writer = new XmlTextWriter(pPath, Encoding.UTF8);
// write a readable file
writer.Formatting = Formatting.Indented;
writer.Indentation = 4;
//writer.Settings.OutputMethod = XmlOutputMethod.// Occorre un'istanza della classe XmlSerializer
XmlSerializer serializer = new XmlSerializer(pTypeToSerialize, pExtraTypes);
// e questo é tutto ciò che serve per persistere i dati
serializer.Serialize(writer, pObjToSerialize);
}
catch (Exception ex)
{
throw new ApplicationException(" " + mClassName + "."
+ System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
}
finally
{
// Chiudere sempre i flussi non appena possibileif (writer != null)
{
writer.Close();
}
}
} Public Shared Sub SerializeToFile(ByVal pPath As String, ByVal pObjToSerialize As Object, _
ByVal pTypeToSerialize As Type, ByVal pExtraTypes As Type())
Dim writer As XmlTextWriter = NothingTry' Creo un flusso per l'operazione di scrittura dei dati su file
writer = New XmlTextWriter(pPath, Encoding.UTF8)
' write a readable file
writer.Formatting = Formatting.Indented
writer.Indentation = 4
'writer.Settings.OutputMethod = XmlOutputMethod. ' Occorre un'istanza della classe XmlSerializer Dim serializer As New XmlSerializer(pTypeToSerialize, pExtraTypes)
' e questo é tutto ciò che serve per persistere i dati
serializer.Serialize(writer, pObjToSerialize)
Catch ex As Exception
Throw New ApplicationException(" " + mClassName + "." _
+ System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex)
Finally' Chiudere sempre i flussi non appena possibile If writer IsNot Nothing Then
writer.Close()
End IfEnd TryEnd SubLa sola cosa che cambia, anche in questo metodo è il costruttore del serializzatore. Il Metodo SerializeToString public static string SerializeToString(object pObjToSerialize)
{
string ret = "";
try
{
MemoryStream stream = new MemoryStream();
XmlSerializer serializer = new XmlSerializer(pObjToSerialize.GetType());
serializer.Serialize(stream, pObjToSerialize);
ret = Encoding.UTF8.GetString(stream.ToArray());
stream.Close();
}
catch (Exception ex)
{
throw new ApplicationException(" " + mClassName + "."
+ System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
}
return (ret);
} Public Shared Function SerializeToString(ByVal pObjToSerialize As Object) As StringDim ret As String = ""TryDim stream As New MemoryStream()
Dim serializer As New XmlSerializer(pObjToSerialize.[GetType]())
serializer.Serialize(stream, pObjToSerialize)
ret = Encoding.UTF8.GetString(stream.ToArray())
stream.Close()
Catch ex As Exception
Throw New ApplicationException(" " + mClassName + "." _
+ System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex)
End TryReturn (ret)
End FunctionQuesto metodo, genera una stringa contenente la serializzazione di un oggetto, per farlo, ancora una volta cambiamo solo il costruttore utilizzato per la generazione della stringa XML ed utilizziamo uno stream, esattamente un MemoryStream, come destinazione, in questo caso non formattiamo l'output, ma volendo potremmo comunque formattare l'output. Vediamo ora anche la versione per gli oggetti complessi, come per gli altri metodi. public static string SerializeToString(object pObjToSerialize, Type[] pExtraTypes)
{
string ret = "";
try
{
MemoryStream stream = new MemoryStream();
XmlSerializer serializer = new XmlSerializer(pObjToSerialize.GetType(), pExtraTypes);
serializer.Serialize(stream, pObjToSerialize);
ret = Encoding.UTF8.GetString(stream.ToArray());
stream.Close();
}
catch (Exception ex)
{
throw new ApplicationException(" " + mClassName + "."
+ System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex);
}
return (ret);
} Public Shared Function SerializeToString(ByVal pObjToSerialize As Object, _
ByVal pExtraTypes As Type()) As StringDim ret As String = ""TryDim stream As New MemoryStream()
Dim serializer As New XmlSerializer(pObjToSerialize.[GetType](), pExtraTypes)
serializer.Serialize(stream, pObjToSerialize)
ret = Encoding.UTF8.GetString(stream.ToArray())
stream.Close()
Catch ex As Exception
Throw New ApplicationException(" " + mClassName + "." + _
System.Reflection.MethodBase.GetCurrentMethod().Name + ": " + ex.Message, ex)
End TryReturn (ret)
End FunctionCome per i precedenti metodi, cambia solo il costruttore del serializzatore. Conclusioni Anche questa classe Helper contribuirà a costruire la nostra libreria di funzioni di base da riutilizzare nei nostri progetti futuri, la terza puntata sarà dedicata a spiegare le nostre prime due interfacce, che assieme alle funzioni di serializzazione utilizzeremo molto spesso. Per qualsiasi Feedback, Ulteriore domanda, Chiarimento, oppure se trovate qualche errore usate direttamente il form dei commenti in calce a questo articolo oppure scrivete all'autrice . |