Valori di default per un ASP.Net WebControl

Nella scorsa settimana mi sono trovato di fronte alla necessità di dover creare un WebControl per ASP.Net al fine di incapsulare una porzione di codice che avrei sicuramente potuto condividere tra più progetti, e la scelta di utilizzare un WebControl era quanto mai sensata.

Al di là dello scopo ultimo del codice, un’aspetto che mi ha tenuto bloccato per alcuni giorni è stato quello di trovare un sistema per dare un valore di default ad alcune proprietà del controllo stesso.

La soluzione in fondo è semplice, il problema è stato l’arrivarci perchè come al solito ci si perde di fronte al gran numero di classi e di override che il .Net mette a disposizione.

Si potrebbe dire semplicemente, come in effetti lo è, che basta aggiungere qualche riga di codice nel Page_Load della pagina che contiene il controllo, con un paio di if controllare se la pagina è appena caricata, oppure se è un PostBack e si agisce di conseguenza. Vero.

Per un attimo però mi sono immedesimato nei panni di un altro sviluppatore che avrebbe potuto usare il mio controllo. Magari uno sviluppatore alle prime armi, che non conoscendo il mio controllo, semplicemente lo trascina nella web page e imposta qualche proprietà, magari dimenticandone qualcuna necessaria al corretto funzionamento del controllo stesso, con l’inevitabile conseguenza che il controllo non funziona a dovere.

Chi conosce gli attributi potrebbe in un primo momento pensare di utilizzare l’attributo DefaultValue. Tuttavia questo attributo è solo una parte di questa soluzione.

Gli attributi forniscono un potente sistema per associare in maniera dichiarativa delle informazioni al codice .Net.

Infatti il DefaultValueAttribute si preoccupa esclusivamente di associare un valore di default alla proprietà. Sembrerebbe quello che fa al caso nostro, ma sfortunatamente quello che non è scritto nell’MSDN è che il comportamento di questo attributo è limitato all’uso con il solo designer. Meglio ancora il DefaultValueAttribute si preoccupa di:

  • Rendere disponibile una funzione di reset in caso in cui il valore della PropertyGrid sia diverso da quello contenuto in questo attributo;
  • Evitare a Visual Studio di serializzare il valore nel markup code quando la proprietà contiene la stessa impostazione dell’attributo;
  • Indica al controllo propertygrid del designer di scrivere in carattere standard (non grassetto) il valore della proprietà quando questa è identica al valore dell’attributo;

Quindi per capirci, l’attributo DefaultValue non ha nulla a che vedere con il senso più generale della parola Default, che lascia immaginare che impostato quell’attributo, la proprietà del controllo alla quale l’attributo è associato in fase di inizializzazione sappia di dover assumere tale valore (Per una soluzione tipo questa, si potrebbe ricorrere ad un condice come quello che ho mostrato in questo post sul DefaultValue di un controllo).

Il controllo, infatti, non sà assolutamente nulla di quel DefaultValue attribute, e quando viene inizializzato, le sue proprietà assumeranno – fatto salvo diverso codice – il valore di default del tipo esposto. Significa che una stringa conterrà uno string.empty, un integer avrà valore 0 e così via.

Si tratta quindi di un problema di inizializzazione?

La cosa potrebbe a questo punto essere risolvibile mettendo questo nostro valore di default all’interno del costruttore della classe che costituisce il nostro web control, ovviando così ad ogni genere di problema. Vero anche questo.

Ma la domanda che voglio porre a questo punto è: e se io volessi rendere libero lo sviluppatore di poter cambiare questi valori di default?

Anche qui la risposta sarebbe “Il tuo sviluppatore semplicemente usando la Page_Load …”; come dare torto a questa affermazione?

Veniamo però al lato pratico: fare un hard-coding dei valori delle proprietà necessarie all’interno del costruttore (Punto 3), significa un millesimale degrado di performance laddove il programmatore a sua volta, con il Page_Load imposta nuovamente i suoi valori di default per quel dato controllo (Punto 4). imageQuesto succede perchè al momento del caricamento della pagina l’ASP.Net Page life cycle funziona così (tralasciando le parti che non ci interessano):

  1. La pagina viene richiesta
  2. La pagina viene inizializzata
  3. I controlli interni vengono istanziati
  4. I controlli vengono inizializzati
  5. La pagina viene caricata

Fosse tutto qui il problema, uno ci potrebbe anche sorvolare sopra. Io volevo comunque dare la possibilità al mio controllo di persistere visivamente questo mio valore di default, cioè fare in modo che usando il designer e la property grid, qualora io imposto una certa proprietà questa mi venga in automatico scritto nel markup code del controllo contenuto nella pagina web.

In effetti questo già avviene, ma con una eccezione riferita all’attributo DefaulValue. Nel caso in cui il nostro web control sia stato appena creato, quindi non abbiamo ancora utilizzato l’attributo, ad ogni modifica delle proprietà tramite la property grid corrisponde la serializzazione del valore nel markup code.

Laddove però si sia utilizzato l’attributo, qualora la proprietà abbia il DefaultValue con lo stesso identico valore impostato nella property grid, il designer ritiene opportuno non serailizzare più il valore e anzi di cancellarlo completamente.

Tradotto questo cosa vuol dire? Che al momento in cui la nostra pagina verrà eseguita, non essendoci del markupcode, questo non verrà tradotto nel corrispettivo codice di inizializzazione e la nostra proprietà varrà niente (stringa vuote, 0, ecc.). Ecco allora il nuovo dilemma.

Come persistere allora il valore di default?

Guardando sempre tra gli attributi, con un pò di pazienza, se ne scorge uno che permette di influenzare il modo in cui il designer tratta il nostro controllo. Si tratta dell’attributo Designer del namespace System.ComponentModel (Per una lista descrittiva di tutti gli attributi potete guardare qui). Sembra quello che fa al caso nostro. Tant’è che in precedenti controlli WinForm ho avuto modo di utilizzarlo per alterare la lista delle proprietà in fare di PrePosting, quindi la classe non mi era affatto nuova.

Dentro ai metodi di cui era possibile fare l’override ne scorgo uno che fa al caso mio: InitializeNewComponent(System.Collections.IDictionary defaultValues), il cui abstract dell’MSDN recitando “Inizializza un componente appena creato” lasciano proprio intendere che questo è quello che serve.

Sulle prime non riesco a trovare alcun esempio per ASP.Net, ma solo per WinForm, ma l’analogia delle classi che ereditano dallo stesso namespace mi invita a provare il medesimo codice, che ovviamente non funziona. Provo dalle cose più semplici a quelle più astruse. Fino a quando apro un incident MSDN per scoprire – sottoponendo tutto questo caso – con l’excalation engineer (quindi un livello sopra il tecnico che ti segue con la prima fase) che questo metodo non funziona.

Anzi, secondo filosofia Microsoft – dice l’ingegnere – tutto questo discorso del DefaultValue, almeno per come l’ho inteso io, non si doveva neanche fare, perchè questa filosofica recita che i controlli devono essere più Smart e più semplici possibili, ovvero dall’esempio che mi ha fatto questo ingegnere già un è troppo.

Continua poi l’ingegnere dicendo che non essendo necessaria alcuna forma di inizializzazione via markup perchè non tutti i programmatori potrebbero utilizzare Visual Studio come designer, sarebbe del tutto inutile far funzionare un metodo del genere.

Può o meno essere condivisibile questa affermazione. Anzi, aggiungo, se davvero è inutile, perchè non mettere tale classe come sealed? Lasciarla è un puro invito a perdere tempo.

Finalmente la soluzione

Giusto per riassumere, visto che è assai facile che leggendo si sia creata un pò di confusione. Quello che volevo ottenere era:

  1. Al trascinamento del controllo nella web page volevo che automaticamente nel markup code venissero scritte quelle che io ritenevo le mie proprietà di default con un valore x;
  2. Utilizzando l’attributo DefaultValue, volevo che in caso di match tra valore attributo e valore proprietà, il markup code rimanesse li dove era;

In merito alla soluzione per il primo punto, visto e considerato l’insuccesso della classe Designer, lo scopo è perfettamente raggiungibile tramite l’attributo ToolBoxData.

Questo attributo indica al designer di interpretare il suo contenuto ogni qual volta che un controllo viene trascinato sulla web form e di utilizzare parte del suo contenuto per comporre il markup code dentro la web page. Dentro a questo attributo semplicemente si scrivono le proprietà che si vuole facciano parte del markup code, quindi quel valore di default visibile che stavo cercando.

Sembrerebbe tutto risolto, ma non è così. Questo giochino infatti funziona bene solamente se decoriamo le nostre proprietà con un ulteriore altro attributo che l’ingegnere ha portato alla mia attenzione.

Si tratta dell’attributo DesignerSerializationVisibility, che una volta impostato con l’enum su DesignerSerializationVisibility.Visible, indica al designer di persistere il valore della proprietà sempre e comunque, anche quando il valore di default dell’attributo corrisponde a quello della proprietà.

Questo risolve un secondo problema, quello del DefaultValue Attribute che cancella il markupcode in caso di corrispondenza tra valore attributo e valore proprietà.

Con questo sistema si ottiene finalmente un vero Default Value per le proprietà dei vostri web control.