Logica di serializzazione XML

martedì 20 maggio 2008 - 14.07

Teech Profilo | Expert

Stò facendo varie prove sulla serializzazione ed ho incontrato delle domande che ritengo interessanti.
Stò costruendo delle classi per gestire in modo flessibile i settaggi (opzioni di configurazione) di una mia applicazione. Questa applicazione necessita di molte opzioni (sono alla base della logica della mia applicazione).
Per gestire queste informazioni ho creato una classe che implementa il pattern Singleton e serializzo/deserializzo questa classe per creare un file XML contenente le configurazioni.
Logicamente mi pare una soluzione ma stò incontrando alcuni problemi a livello logico che mi piacerebbe condividere e risolvere con voi.

I dati dei settaggi sono logicamente organizzabili per in gruppi ben definiti: ad esempio, l'applicazione si dovrà connettere a 2 database diversi e quindi avrò i dati di Server, User, Password e NomeDB per ognuna delle 2 connessioni.
Per fare questo posso fare:

<XmlRoot(ElementName:="Configurazione")> _ Public Class MieConfig 'Oggetto per il controllo del singleton Private Shared _Values As MieConfig Private Const _FileConfig As String = "Config.xml" Shared Sub New() 'Utile a rendere singleton la classe Load() End Sub Public Shared ReadOnly Property Value() As Config Get 'Controlla che non esista un'istanza della classe (Singleton) If (_Values Is Nothing) Then 'istanzia la classe solo se non già istanziata _Values = New MieConfig End If 'Ritorna la classe istanziata Return _Values End Get End Property Friend Shared Sub Load() If IO.File.Exists(_FileConfig) Then 'Deserializza il file XML Using fs As New IO.FileStream(_FileConfig, IO.FileMode.Open) Dim xser As New Xml.Serialization.XmlSerializer(GetType(MieConfig)) _Values = DirectCast(xser.Deserialize(fs), MieConfig) End Using End If End Sub Public Shared Sub Save() 'Serializza il file XML Using fs As New IO.FileStream(_FileConfig, IO.FileMode.Create) Dim xser As New Xml.Serialization.XmlSerializer(GetType(MieConfig)) xser.Serialize(fs, _Values) End Using End Sub #Region "Proprietà" Private _Server1 As String <XmlElement(ElementName:="Server1")> _ Public Property Server1() As String Get Return _Server1 End Get Set(ByVal value As String) _Server1 = value End Set End Property Private _Server2 As String <XmlElement(ElementName:="Server2")> _ Public Property Server2() As String Get Return _Server2 End Get Set(ByVal value As String) _Server2 = value End Set End Property Private _User1 As String <XmlElement(ElementName:="User1")> _ Public Property User1() As String Get Return _User1 End Get Set(ByVal value As String) _User1 = value End Set End Property Private _User2 As String <XmlElement(ElementName:="User2")> _ Public Property User2() As String Get Return _User2 End Get Set(ByVal value As String) _User2 = value End Set End Property '... e così via #End Region End Class

Ho provato, attraverso le classi ad organizzare meglio i dati e ci sono riuscito in questo modo:

<XmlRoot(ElementName:="Configurazione")> _ Public Class MieConfig 'Oggetto per il controllo del singleton Private Shared _Values As MieConfig Private Const _FileConfig As String = "Config.xml" Shared Sub New() 'Utile a rendere singleton la classe Load() End Sub Public Shared ReadOnly Property Value() As MieConfig Get 'Controlla che non esista un'istanza della classe (Singleton) If (_Values Is Nothing) Then 'istanzia la classe solo se non già istanziata _Values = New MieConfig End If 'Ritorna la classe istanziata Return _Values End Get End Property Friend Shared Sub Load() If IO.File.Exists(_FileConfig) Then 'Deserializza il file XML Using fs As New IO.FileStream(_FileConfig, IO.FileMode.Open) Dim xser As New Xml.Serialization.XmlSerializer(GetType(MieConfig)) _Values = DirectCast(xser.Deserialize(fs), MieConfig) End Using End If End Sub Public Shared Sub Save() 'Serializza il file XML Using fs As New IO.FileStream(_FileConfig, IO.FileMode.Create) Dim xser As New Xml.Serialization.XmlSerializer(GetType(MieConfig)) xser.Serialize(fs, _Values) End Using End Sub #Region "Proprietà" Private _Database1 As New Database1Class <XmlElement(ElementName:="Database1")> _ Public Property Database1() As Database1Class Get Return _Database1 End Get Set(ByVal value As Database1Class) _Database1 = value End Set End Property Private _Database1 As New Database2Class <XmlElement(ElementName:="Database2")> _ Public Property Database2() As Database2Class Get Return _Database2 End Get Set(ByVal value As Database2Class) _Database2 = value End Set End Property #End Region End Class Public Class Database1Class #Region "Proprietà" Private _Server As String = "" <XmlElement(ElementName:="Server")> _ Public Property Server() As String Get Return _Server End Get Set(ByVal value As String) _Server = value End Set End Property Private _User As String = "" <XmlElement(ElementName:="User")> _ Public Property User() As String Get Return _User End Get Set(ByVal value As String) _User = value End Set End Property '... e cos' via #End Region End Class Public Class Database2Class #Region "Proprietà" Private _Server As String = "" <XmlElement(ElementName:="Server")> _ Public Property Server() As String Get Return _Server End Get Set(ByVal value As String) _Server = value End Set End Property Private _User As String = "" <XmlElement(ElementName:="User")> _ Public Property User() As String Get Return _User End Get Set(ByVal value As String) _User = value End Set End Property '... e così via #End Region End Class

Fin qui tutto OKe funziona, però mi ritrovo ad un bivio perchè vorrei sistemare alcune cose che non mi piacciono.

In primo luogo vorrei che le classi Database1 e Database2 fossero visibili solo dalla classe MieConfig perchè sono a suo esclusivo uso e consumo. Non riesco a farlo perchè se cambio il modificatore di accesso non posso definire le property nella classe MieConfig come Public e quindi non verrebbero serializzate da XMLSerialize.

Ho pensato di gestire le classi Database1 e Database2 come pubbliche aggiungendo altre proprietà, funzioni e metodi che mi permettano di gestire i DB in modo più mirato. Facendo ciò però incontrerei un problema successivo nel momento in cui volessi serializzare le singole classi: per serializzare la classe MieConfig mi servono alcune proprietà delle classi Database1 e Database2 ma serializzando la classe Database1 (ad esempio) vorrei poter serializzare altre proprietà pubbliche eventualmente con Attributi diversi...

Come posso risolvere questi "problemi"? Che consigli/dritte potete darmi? Cose ne pensate?
Grazie a tutti quelli che hanno avuto la pazienza di leggere e cercare di capire le mie elucubrazioni mentali

Ciao!!!
--------------
Maurizio Brini
--------------
Nessuna impresa è mai stata compiuta da un uomo ragionevole

rossimarko Profilo | Guru

Ciao

>
>In primo luogo vorrei che le classi Database1 e Database2 fossero
>visibili solo dalla classe MieConfig perchè sono a suo esclusivo
>uso e consumo. Non riesco a farlo perchè se cambio il modificatore
>di accesso non posso definire le property nella classe MieConfig
>come Public e quindi non verrebbero serializzate da XMLSerialize.
>

Per questa problematica non vedo molte soluzioni. Purtroppo se non metti la classe public non potrai vederla dagli oggetti esterni e quindi neanche pubblicarla come tipo di ritorno della property. Se il tuo desiderio è rendere le proprietà readonly allora dovrai farti uno strado intermedio che carica i dati dall'xml e li espone all'interno di una classe con le property readonly.

>Ho pensato di gestire le classi Database1 e Database2 come pubbliche
>aggiungendo altre proprietà, funzioni e metodi che mi permettano
>di gestire i DB in modo più mirato. Facendo ciò però incontrerei
>un problema successivo nel momento in cui volessi serializzare
>le singole classi: per serializzare la classe MieConfig mi servono
>alcune proprietà delle classi Database1 e Database2 ma serializzando
>la classe Database1 (ad esempio) vorrei poter serializzare altre
>proprietà pubbliche eventualmente con Attributi diversi...
>

Scusami, ma qui non ho capito molto bene. Potresti farmi un esempio di quello che ti serve?

>Come posso risolvere questi "problemi"? Che consigli/dritte potete
>darmi? Cose ne pensate?

Dal mio punto di vista le due classi DatabaseClass1 e DataBaseClass2 potrebbero essere raggruppate in un'unica classe DataBaseClass che poi viene utilizzata dalle due property:

Private _Database1 As New DatabaseClass <XmlElement(ElementName:="Database1")> _ Public Property Database1() As DatabaseClass Get Return _Database1 End Get Set(ByVal value As DatabaseClass) _Database1 = value End Set End Property Private _Database2 As New DatabaseClass <XmlElement(ElementName:="Database2")> _ Public Property Database2() As DatabaseClass Get Return _Database2 End Get Set(ByVal value As DatabaseClass) _Database2 = value End Set End Property
-----------------------------------------
Rossi Marco
http://blogs.dotnethell.it/rossimarko

Teech Profilo | Expert

Purtroppo ora sono davanti ad un PC dove non ho il progetto (ho spento il portatile)
Penso che farò il merge delle classi Database1Class e Database2Class come consigliato

In definitiva vorrei trovarmi con un file XML ben elaborato con attributi, proprietà e nodi ben strutturati utilizzando la serializzazione per gestire un file di configurazione. Per fare ciò avevo pensato di organizzare le proprietà serializzate in classi onde avere una struttura corretta nel file XML per ciò che rigurda i Nodes.
Se, ad esempio, nella classe Database1Class (che ricordo è pubblica) volessi aggiungere una proprietà pubblica (MiaProprietà) per utilizzarla in contesti diversi da quelli del file di configurazione questa verrebbe serializzata anche nel file di configurazione a meno che non utilizzi l'attributo <XMLIgnore()>. Ma se volessi crearmi una serializzazione della classe che include anche la property MiaProprietà mi ritrovo castrato...

Vorrei aggiungere un'altra opportunità a quanto detto in precedenza per risolvere i problemi di scope delle classi. La classe DatabaseClass la dichiaro come Friend all'interno dell'assembly e dichiaro in MieConfig la variabile privata _Database1.
A questo punto creo tante Public Property in MieConfig che mappano solo le proprietà che mi interessa (mettendo gli attributi di serializzazione sulle classi di MieConfig) serializzare della classe DatabaseClass.
Attualmente questa soluzione è quella che più mi piace ma in questo modo tutti gli attributi nel file XML risultano come un elenco e questo preferirei non fosse...
A questo punto giro una nuova domanda: è possibile organizzare gli attributi del file XML attraverso la serializzazione? E' possibile, ad esempio, aggiungere nodi ai quali associare gli attributi che derivano dalle prorpietà della classe?

Provo a correggere manualmente il codice postato in precedenza per maggiore chiarezza e domani posterò il risultato XML che ho ora e quello che vorrei avere...

<XmlRoot(ElementName:="Configurazione")> _ Public Class MieConfig 'Oggetto per il controllo del singleton Private Shared _Values As MieConfig Private Const _FileConfig As String = "Config.xml" Shared Sub New() 'Utile a rendere singleton la classe Load() End Sub Public Shared ReadOnly Property Value() As MieConfig Get 'Controlla che non esista un'istanza della classe (Singleton) If (_Values Is Nothing) Then 'istanzia la classe solo se non già istanziata _Values = New MieConfig End If 'Ritorna la classe istanziata Return _Values End Get End Property Friend Shared Sub Load() If IO.File.Exists(_FileConfig) Then 'Deserializza il file XML Using fs As New IO.FileStream(_FileConfig, IO.FileMode.Open) Dim xser As New Xml.Serialization.XmlSerializer(GetType(MieConfig)) _Values = DirectCast(xser.Deserialize(fs), MieConfig) End Using End If End Sub Public Shared Sub Save() 'Serializza il file XML Using fs As New IO.FileStream(_FileConfig, IO.FileMode.Create) Dim xser As New Xml.Serialization.XmlSerializer(GetType(MieConfig)) xser.Serialize(fs, _Values) End Using End Sub #Region "Proprietà" Private _Database1 As New DatabaseClass <XmlElement(ElementName:="Server1")> _ Public Property Server1() As String Get Return _Database1.Server End Get Set(ByVal value As String) _Database1.Server = value End Set End Property <XmlElement(ElementName:="User1")> _ Public Property User1() As String Get Return _Database1.User End Get Set(ByVal value As String) _Database1.User = value End Set End Property Private _Database2 As New DatabaseClass <XmlElement(ElementName:="Server2")> _ Public Property Server2() As String Get Return _Database2.Server End Get Set(ByVal value As String) _Database2.Server = value End Set End Property <XmlElement(ElementName:="User2")> _ Public Property User2() As String Get Return _Database2.User End Get Set(ByVal value As String) _Database2.User = value End Set End Property '...e così via... #End Region End Class Friend Class DatabaseClass #Region "Proprietà" Private _Server As String = "" Public Property Server() As String Get Return _Server End Get Set(ByVal value As String) _Server = value End Set End Property Private _User As String = "" Public Property User() As String Get Return _User End Get Set(ByVal value As String) _User = value End Set End Property '... e cos' via #End Region End Class

Spero di essere stato chiaro...
So che probabilmente è solo un vezzo (a questo punto) però, visto che stò studiando la serializzazione, perchè non fare le cose fatte bene? Poi spero che a qualcuno, il codice che ho postato possa servire come spunto (sarà perchè l'ho fatto ma a me piace)

Grazie mille

Ciao!!!
--------------
Maurizio Brini
--------------
Nessuna impresa è mai stata compiuta da un uomo ragionevole

Teech Profilo | Expert

Rieccomi...

Il file XML Che ottengo ora è il seguente:

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra
Quello che vorrei ottenere è il seguente:
Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra
Il tutto mantenendo i modificatori di accesso nelle classi che si comportino come nel mio attuale codice...
Ritenete sia possibile? Eventualmente, come?
Lo ritengo importante perchè alla fine questo "modulo" dell'applicazione sarà uno dei più importanti e credo che supererò le 50 proprietà nella classe Config...

Grazie mille!!!

Ciao!!!
--------------
Maurizio Brini
--------------
Nessuna impresa è mai stata compiuta da un uomo ragionevole

rossimarko Profilo | Guru

Ciao,

scusami ma il file
Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra

io lo ottengo già con questa classe

Imports System.Xml.Serialization <XmlRoot(ElementName:="Configurazione")> _ Public Class MieConfig 'Oggetto per il controllo del singleton Private Shared _Values As MieConfig Private Const _FileConfig As String = "d:\Config.xml" Shared Sub New() 'Utile a rendere singleton la classe Load() End Sub Public Shared ReadOnly Property Value() As MieConfig Get 'Controlla che non esista un'istanza della classe (Singleton) If (_Values Is Nothing) Then 'istanzia la classe solo se non già istanziata _Values = New MieConfig End If 'Ritorna la classe istanziata Return _Values End Get End Property Friend Shared Sub Load() If IO.File.Exists(_FileConfig) Then 'Deserializza il file XML Using fs As New IO.FileStream(_FileConfig, IO.FileMode.Open) Dim xser As New Xml.Serialization.XmlSerializer(GetType(MieConfig)) _Values = DirectCast(xser.Deserialize(fs), MieConfig) End Using End If End Sub Public Shared Sub Save() 'Serializza il file XML Using fs As New IO.FileStream(_FileConfig, IO.FileMode.Create) Dim xser As New Xml.Serialization.XmlSerializer(GetType(MieConfig)) xser.Serialize(fs, _Values) End Using End Sub #Region "Proprietà" Private _Database1 As New DatabaseClass <XmlElement(ElementName:="Database1")> _ Public Property Database1() As DatabaseClass Get Return _Database1 End Get Set(ByVal value As DatabaseClass) _Database1 = value End Set End Property Private _Database2 As New DatabaseClass <XmlElement(ElementName:="Database2")> _ Public Property Database2() As DatabaseClass Get Return _Database2 End Get Set(ByVal value As DatabaseClass) _Database2 = value End Set End Property #End Region End Class Public Class DatabaseClass #Region "Proprietà" Private _Server As String = "" <XmlElement(ElementName:="Server")> _ Public Property Server() As String Get Return _Server End Get Set(ByVal value As String) _Server = value End Set End Property Private _User As String = "" <XmlElement(ElementName:="User")> _ Public Property User() As String Get Return _User End Get Set(ByVal value As String) _User = value End Set End Property '... e cos' via #End Region End Class


E' questo quello che intendevi?
-----------------------------------------
Rossi Marco
http://blogs.dotnethell.it/rossimarko

Teech Profilo | Expert

Scusami il ritardo...

Sono d'accordo anch'io che l'impostazione che hai proposto crea una formattazione idonea però la classe DatabaseClass è pubblica, mentre io vorrei che fosse a solo uso e consumo della classe che gestisce le configurazioni... Io avevo creato la classe con scope Friend però in questo modo non posso dimensionare le proprietà (che devono essere Public per poter essere serializzate) alla classe...

Forse l'approccio della serializzazione non è il più corretto per lo scopo che mi sono prefissato e dovrei gestire un XML con il DOM... Speravo di non doverlo fare in quanto non sono ferratissimo in XML ma posso sempre coglierla come occasione per studiare
Se avete altre soluzioni sono bene accette...

Grazie!!!
--------------
Maurizio Brini
--------------
Nessuna impresa è mai stata compiuta da un uomo ragionevole

rossimarko Profilo | Guru

Ma perchè non vuoi che le classi siano pubbliche? Dal momento che le esponi è inevitabile, altrimenti non riusciresti a leggerne le proprietà.

Se vuoi che siano readonly allora devi fare una classe apposta che si legga i dati da quella serializzata
-----------------------------------------
Rossi Marco
http://blogs.dotnethell.it/rossimarko

Teech Profilo | Expert

Nel mio scenario la classe delle configurazioni l'ho impostata Singleton perchè le configurazioni saranno valide su tutto il progetto e quindi vorrei che fossero istanziate solo una volta. Se all'interno della classe Singleton istanzio delle classi pubbliche, a livello strettamente logico, il pattern Singleton perde di valore in quanto parte della classe può essere istanziata più volte.

Siccome la serializzazione necessita di proprietà/funzioni pubbliche forse, l'approccio che ho scelto è logicamente errato (eppure ci sono andato così vicino)... o forse stò sbagliando qualcosa nel mio ragionamento...
--------------
Maurizio Brini
--------------
Nessuna impresa è mai stata compiuta da un uomo ragionevole

rossimarko Profilo | Guru

>Nel mio scenario la classe delle configurazioni l'ho impostata
>Singleton perchè le configurazioni saranno valide su tutto il
>progetto e quindi vorrei che fossero istanziate solo una volta.
>Se all'interno della classe Singleton istanzio delle classi pubbliche,
>a livello strettamente logico, il pattern Singleton perde di
>valore in quanto parte della classe può essere istanziata più
>volte.
>
>Siccome la serializzazione necessita di proprietà/funzioni pubbliche
>forse, l'approccio che ho scelto è logicamente errato (eppure
>ci sono andato così vicino)... o forse stò sbagliando qualcosa
>nel mio ragionamento...
>--------------
>Maurizio Brini
>--------------
>Nessuna impresa è mai stata compiuta da un uomo ragionevole

Da quello che ho visto nel codice la classe principale è singleton e quindi sarà istanziata una sola volta.

Per quanto riguarda le classi contenute non devi porti il problema, perchè comunque saranno istanziate una sola volta, in quanto le variabili sono contenute nella classe singleton. Dal mio punto di vista hai raggiunto lo scopo. Prova eventualmente a fare delle verifiche con dei breakpoint nei vari costruttori per controllare il flusso logico.

-----------------------------------------
Rossi Marco
http://blogs.dotnethell.it/rossimarko
Partecipa anche tu! Registrati!
Hai bisogno di aiuto ?
Perchè non ti registri subito?

Dopo esserti registrato potrai chiedere
aiuto sul nostro Forum oppure aiutare gli altri

Consulta le Stanze disponibili.

Registrati ora !
Copyright © dotNetHell.it 2002-2024
Running on Windows Server 2008 R2 Standard, SQL Server 2012 & ASP.NET 3.5