Premessa
Nel mio caso io ho lavorato sul web.config, ma dato che non ho fatto nulla di tracendentale e ho utilizzato solo classi del framework e nulla di personalizzato, presumo che se il file di configurazione sia quello di un applicativo, non dovrebbero esserci problemi.
Ricordo che una soluzione per antonomasia non esiste, questo è chiaro. Sulle prima, infatti, non ero nemmeno sicuro che questa fosse quella che faceva al caso mio, e tra l'altro avevo anche dei problemi nel farla funzionare, sicchè mi venne suggerito di usare un semplice schema XML/XSD, che se sulle prime poteva sembrare efficace, immaginando meglio lo scenario finale, risultava poco "sicuro" per via del fatto che chiunque avesse avuto accesso al sorgente del sito avrebbe potuto variare il contenuto del valore da validare. Quindi sono ritornato sui miei passi e ho iniziato a capirci qualcosa dei Validator.
Al lavoro dunque!
La necessità
Poter controllare cosa accidenti scrive l'utente in un file di configurazione.
Soluzione
Sulle prime quando ho cercato di capire come potessi fare a validare, la prima ricerca che ho fatto è stata proprio "Custom Validator" e - benchè mi pare di capire che questo sia il nome di quello di cui sto parlando -, tutti i risultati che venivano fuori (Msdn, google, ecc.) erano riferiti a custom validator che però si associavano esclusivamente a controlli sulle webform. Il che non era esattamente quello che volevo.
Mi sono guardato un attimo intorno, ed in particolare sul fatto di cosa una custom configSections (per crearla bisogna ereditare da ConfigurationSection) e i suoi elementi (creabili da ConfigurationElement) e qui erano saltati fuori StringValidator e compagni.
Erano esattamente quello che mi serviva. Allora, come da ogni buon programmatore (??) guardo da che classe ereditano ... e nel caso dello StringValidator, da StringValidatorAttribute che come unica proprietà da implementare richiede la ValidatorInstance, una proprietà di sola lettura che restituisce un oggetto, la vostra classe, di tipo ConfigurationValidatorBase che si occuperà di fare il lavoro "sporco" e di validare il contenuto dell'attributo del file di configurazione, richiamando dapprima il metodo CanValidate per verificare che il tipo di dati datogli in pasto sia validabile secondo la nostra classe e poi il rispettivo Validate per sapere se il valore è o meno valido.
Reperite queste informazioni, il gioco è stato abbastanza (??) semplice.
Ma vediamo qualche linea di codice:
public sealed class NomeValidatorAttribute
: ConfigurationValidatorAttribute
{
NomeValidator istance;
public NomeValidatorAttribute()
{
}
public override ConfigurationValidatorBase
ValidatorInstance
{
get
{
if (istance == null)
istance = new NomeValidator();
return istance;
}
}
}
Con questa classe - non ereditabile (non sia mai qualcuno tocchi il mio codice) - qui sopra non faccio altro che definire un oggetto di tipo attributo (perchè eredita da ConfigurationBaseAttribute che a sua volta eredita da Attribute) che chiamo - guarda caso per ricordarmi - con il nome del mio attributo presente nel file di configurazione con l'aggiunta del suffisso Validator tanto per rimanere in stile Microsoft (La parola Attribute che segue Validator sebbene non strettamente indispensabile e caldamente ignorata dall'IDE ce l'ho aggiunta per la stessa identica ragione - rimanere con una classe stile Microsoft).
All'interno dichiaro una variabile globale di tipo Validatore (che ancora non ho mostrato), il costruttore di default, e l'impementazione della proprietà ValidatorInstance.
Non mi soffermo di più sul fatto che all'interno della classe si potevano creare dei NamedParameter con i quali poter accettare dei valori opzionali in ingresso, o ulteriori costruttori firmati. Ma per lasciare le cose semplici, supponiamo che a noi non interessano parametri.
A questo punto, dobbiamo scrivere la nostra classe che si occupa di validare il contenuto dell'attributo. Ecco qua:
public class ContentTypeValidator
: ConfigurationValidatorBase
{
String[] ct = new String[] {"Andrea", "Giuseppe"};
public ContentTypeValidator()
{
}
public override bool CanValidate(Type type)
{
return type == typeof(string);
}
public override void Validate(object value)
{
if (Array.IndexOf(ct, value) == -1)
throw new ConfigurationErrorsException(
"The name value supplied isn't allowed");
}
}
Ok, forse non sarà il massimo dell'eleganza dichiarare quell'array lassu, secco con due valori e francamente in un caso dove realmente dovrei validare il nome scegliendolo da una lista di possibili valori, sinceramente ho qualche difficoltà anche io a capire come andarli a leggere da un elenco più nutrito che non mi sia scritto prima a mano. Ma questo è pur sempre un esempio.
Questa classe, come dicevo prima, controlla il contenuto dapprima verificando che il tipo in ingresso sia valido e poi validandolo, e in caso negativo si preoccupa di sollevare un'eccezione al compilatore con relativo messaggio che aiuti a capire cosa è successo.
Come uso il tutto? Intanto prendendo le due classi e mettendole nel codice, poi, sempre premesso che stiate lavorando su di un configuration element, non dovrete far altro che andare al di sotto della vostro elemento,
mettere una parentesi quadra per aggiungere un attributo ed ecco che magicamente compare nella nostra lista il nostro validatore.
Et voilà, il gioco è fatto.