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.
