Non lo nego: questa volta ci sono andato un po' pesante.
Sto scrivendo questa prima linea del mio post, ma so già fin dal principio
che sarà bello lungo. Sì, Lorenzo, non
sgridarmi, appena clicco Salva e Pubblica del mio IMHO, vado subito in
.TEXT e vedo di sistemare le cose, ok? Promesso!
A parte gli scherzi, credo che questa sia la prima volta in cui credo che non
riuscirò ad essere chiaro fino in fondo, soprattutto perchè bisognerebbe avere
davanti il codice di cui si parla e, per dirla tutta, anche metterci un po' le
mani, capire perchè ho fatto così, come metter giù un modo migliore
(c'è sempre un modo migliore) e così via.
Obiettivo - Creare un custom visualizer per la mia
XMLSerialization
Internet è piena di tutorial che spiegano come
creare un custom visualizer, ovvero un viewer specifico per una classe che ci
possa aiutare e semplificare il lavoro durante il debug della classe
stessa. Ripeto il concetto, con parole diverse: se stiamo debuggando del codice
con alcuni oggetti string, questi oggetti li vediamo
nelle finestre Locals, Watch, etc. Inoltre, VS2005 ci mette a disposizione un
tooltip che ci mostra il valore della variabile string. Una string
è semplice, non ci sono grossi problemi. Ora, immaginatevi la stessa
situazione con un oggetto molto più complesso (uno qualsiasi del FX, o una
classe scritta da noi): in queste condizioni, VS2005 non può fare altro che
farci vedere una UI standard: una sorta di treeview che possiamo
espandere ad n livelli per leggerne i membri a cascata. Tutto molto bello,
non c'è dubbio, ma VS2005 - non contento di ciò - ci dà una marcia in più:
il custom visualizer.
I tutorial sul Web creano tutti un custom visualizer per le stringhe: tale
visualizer mostra la stringa in una MessageBox, o al massimo in una Windows Form
dedicata. Io ho voluto spingermi un po' più in là, e lavorare invece sulla mia
classe XMLSerialization, che contiene un oggetto di
tipo XmlWriterSettings. Quest'ultima
classe espone alcune proprietà che regolano il modo con cui il FX
serializza una classe: indentazione dei tag, encoding, checking dei caratteri,
quando andare a capo, e così via.
Tale classe è sealed e per
default non è serializzabile (lo sottolineo perchè è
importante).
Ma arriviamo al dunque, una sorta di step-by-step, per creare un custom
visualizer:
- Occorre innanzitutto creare un nuovo progetto di tipo Class
Library alla nostra soluzione
- A questo nuovo progetto, occorre aggiungere una
reference a Microsoft.VisualStudio.DebuggerVisualizers e - più in generale - a
qualsiasi altro assembly di cui abbiamo bisogno
- Scrivere una classe (che io seguendo i tutorial ho preso l'abitudine di
chiamare DebuggerSide) che eredita da
DialogDebuggerVisualizer
- In questa classe, fare l'overloading del metodo Show:
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{ }
In ultimo, firmare l'assembly con uno strong-name. Teoricamente,
dovremmo decorare la classe con l'attributo
DebuggerVisualizer, ma in
realtà non è veramente necessario, e vedremo il perchè. Seguendo le indicazioni
qui sopra, ho creato un progetto XMLSerializationVisualizer
, il cui codice è il seguente (mi rendo conto di
essere un po' frettoloso, per cui vi rimando a questo ottimo walkthrough su MSDN):
using System;
using System.Windows.Forms;
using Microsoft.VisualStudio.DebuggerVisualizers;
namespace XMLSerializationVisualizer
{
public class DebuggerSide : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
XMLSerialization objSer = (XMLSerialization) objectProvider.GetObject();
string msg = string.Format("XMLSerialization Visualizer...{0}", objSer.ToString());
MessageBox.Show(msg);
}
public static void TestShowVisualizer(object objectToVisualize)
{
VisualizerDevelopmentHost vHost = new VisualizerDevelopmentHost(objectToVisualize, typeof(DebuggerSide));
vHost.ShowVisualizer();
}
}
}
Nel metodo Show() possiamo scrivere tutto il codice
che vogliamo. Notare la chiamata a objectProvider.GetObject(), che non fa altro che
reperire l'oggetto dall'engine del debugger di VS2005, castandolo al
tipo corretto che vogliamo gestire. Il metodo statico
TestShowVisualizer() è molto comodo per debuggare il custom
visualizer dal processo chiamante.
Il processo chiamante, ovvero il codice che stiamo debuggando, attiverà
l'istanza del nostro custom visualizer in modo molto semplice, sfruttando
proprio il metodo statico di cui abbiamo appena parlato.
Ecco il codice del
mio Program.cs:
XMLSerialization ser = new XMLSerialization();
XMLSerializationVisualizer.DebuggerSide.TestShowVisualizer(ser);
Il codice qui sopra provoca l'esecuzione del custom visualizer, ovvero il suo
metodo Show() che non fa nient'altro che recuperare l'istanza
di XMLSerialization che abbiamo passato ed elaborarla come gli abbiamo detto
(adesso non è molto utile, mostro in una MessageBox la ToString() senza fare
nulla di particolare).
Il vero problema è che il
debugger di VS2005 usa la serializzazione per passare i dati dal suo IDE ad un
custom visualizer
, per cui dobbiamo
obbligatoriamente fare in modo che la classe che vogliamo gestire nel custom
visualizer sia, per l'appunto, serializzabile. Questo ha richiesto un po' di
refactoring del codice.
Il vero problema è che...XMLSerialization non è
serializzabile!!!
Più che creare il custom visualizer in sè, ho dovuto
aggirare un altro ostacolo: la mia classe XMLSerialization non era
serializzabile, e tantomeno la XmlWriterSettings del FX. Per risolvere, ho
deciso di implementare l'interfaccia ISerializable di cui abbiamo già parlato.
Tale interfaccia ci obbliga a scrivere il codice del metodo
GetObjectData(), che riporto qui sotto:
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Indent", regole.Indent);
info.AddValue("IndentChars", regole.IndentChars);
info.AddValue("ConformanceLevel", regole.ConformanceLevel);
info.AddValue("NewLineOnAttributes", regole.NewLineOnAttributes);
info.AddValue("Encoding", regole.Encoding);
}
Il codice qui sopra viene consumato dal FX ogni volta che chiediamo la
serializzazione binaria della classe (quindi usando
BinaryFormatter piuttosto che XmlSerializer). Il FX intercetta la richiesta di
serializzazione ed esegue GetObjectData() che è a nostra
completa disposizione: nel nostro caso, aggiungo a SerializationInfo tutti i
valori che voglio serializzare (l'oggetto regole che vedete nel
codice è un'istanza di XmlWriterSettings).
Ma non
finisce qui, perchè ad ogni serializzazione corrisponde sempre una
deserializzazione.
Di conseguenza, come
vuole il FX, ho dovuto creare un nuovo costruttore chiamato automaticamente per
creare un'istanza dell'oggetto in fase di serializzazione. Il costruttore è il
seguente:
// Costruttore chiamato durante il deserialize
public XMLSerialization(SerializationInfo info, StreamingContext context)
{
regole = new XmlWriterSettings();
regole.Indent = info.GetBoolean("Indent");
regole.IndentChars = info.GetString("IndentChars");
regole.ConformanceLevel = (ConformanceLevel)info.GetInt32("ConformanceLevel");
regole.NewLineOnAttributes = info.GetBoolean("NewLineOnAttributes");
regole.Encoding = (System.Text.Encoding)info.GetValue("Encoding", typeof(System.Text.Encoding));
}
Concludendo...
Fatto questo, fatto tutto. La cosa davvero
interessante è che, decorando la classe XMLSerializationVisualizer con
l'attributo DebuggerVisualizer nel modo seguente....
[assembly: System.Diagnostics.DebuggerVisualizer(
typeof(XMLSerializationVisualizer.DebuggerSide),
typeof(VisualizerObjectSource),
Target = typeof(XMLSerialization),
Description = "Visualizer per XMLSerialization")]
....otteniamo un bel assembly DLL che possiamo copiare in MyDocuments\Visual
Studio 2005\Visualizers per abilitare il custom visualizer senza avere il
progetto nella nostra soluzione. Questo provoca la comparsa di una lente
d'ingrandimento nelle varie finestre dell'IDE di VS2005 che apre il custom
visualizer: di fatto, non abbiamo più bisogno di inserire nel nostro codice la
chiamata al metodo statico TestShowVisualizer() che abbiamo
visto prima.
Download del codice
Ho intenzione di rilasciare il codice
C# che ho scritto finora. Contempla 3 assembly totali: BusinessObjects, MCPD e XMLSerializationVisualizer. Il codice riassume un
po' quello fatto fino ad oggi: classi, serializzazione, interfacce, ed anche il
custom visualizer descritto in questo post. Aspettatevi quindi un post dedicato,
magari già oggi pomeriggio, con tutto il materiale.