Press "Enter" to skip to content

2 – Classi di uso comune

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.