Il secondo articolo dedicato alle classi di uso generico, i cosiddetti Helper che in seguito serviranno all’interno di vari progetti didattici e potranno ovviamente essere incorporate nelle librerie di classi helper dai nostri lettori.
Ogni programmatore che intenda sviluppare più di una applicazione nella propria carriera, incontra durante la creazione del codice degli oggetti di uso comune, usati N volte all’interno di qualsiasi progetto. Questa serie di articoli, ospiterà questo “Fritto Misto” di classi utili per vari scopi e vari usi, che daranno origine ad una, più probabilmente una serie, di librerie di classi che saranno utilizzate in seguito nei progetti didattici che costruiranno vere e proprie applicazioni.
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 Sub Private Shared ReadOnly mClassName As String _ = System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.Name End Class End Namespace
La 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 Function
Questo 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 Object Dim reader As XmlTextReader = Nothing Dim ret As Object = Nothing Try ' 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) Finally If reader IsNot Nothing Then reader.Close() End If End Try Return (ret) End Function
Il 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 Object Dim reader As XmlTextReader = Nothing Dim ret As Object = Nothing Try ' 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) Finally If reader IsNot Nothing Then reader.Close() End If End Try Return (ret) End Function
In 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 Object Dim ret As Object = Nothing Try Dim 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 Try Return (ret) End Function
Questo 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 Object Dim ret As Object = Nothing Try Dim 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 Try Return (ret) End Function
La 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 possibile if (writer != null) { writer.Close(); } } }
Public Shared Sub SerializeToFile(ByVal pPath As String, ByVal pObjToSerialize _ As Object, ByVal pTypeToSerialize As Type) Dim writer As XmlTextWriter = Nothing 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 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 If End Try End Sub
Con 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 possibile if (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 = Nothing 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 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 If End Try End Sub
La 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 String Dim ret As String = "" Try Dim 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 Try Return (ret) End Function
Questo 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 String Dim ret As String = "" Try Dim 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 Try Return (ret) End Function
Come 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 di contatto con un click sulla bustina in cima alla pagina.
Potete scaricare il progetto esempio dedicato alle classi di uso comune dal link qui sotto indicato.