Ieri sera, mentre riscrivevo alcune delle baseclass
del mio domain model sfruttando alcune delle chicche del FX 2.0
(leggi BindingList<T>, che mi fa risparmiare un sacco di codice), mi sono
chiesto se non fosse possibile costruire una struttura per validare
automaticamente le mie business entities, visto che il codice da scrivere è
spesso ripetitivo: questa proprietà non può essere nulla, quest'altra deve
essere maggiore di zero, e così via.
In pratica, ciò che si vuol fare è di integrare i metadati delle nostre classi decorandole
con Attributi personalizzati che poi possiamo recuperare a
runtime via reflection. Supponiamo, ad esempio, di voler realizzare un
RequiredAttribute per indicare che una certa proprietà non può
assumere valore null:
[AttributeUsage(AttributeTargets.Property,
AllowMultiple=false)]
public class RequiredAttribute: Attribute
{
public RequiredAttribute() { }
public bool IsValid(PropertyInfo property, object objectToValidate)
{
object value = property.GetValue(objectToValidate);
if (value == null)
return false;
if ((value is string) && ((string) value).Length == 0)
return false;
return true;
}
}
La primissima riga è un attributo da applicare alla
nostra classe per indicare che un RequiredAttribute (e mai più di uno) sarà
applicabile solo ad una proprietà (non ad assembly o a classi, ad esempio,
non avrebbe alcun senso!), pena un errore di compilazione. Ciò
che poi dobbiamo fare è solo creare una classe che erediti da
System.Attribute, dotarla di un costruttore ed eventualmente di
proprietà/metodi. Nel mio caso specifico, essendo RequiredAttribute molto
semplice, non ho bisogno di proprietà aggiuntive, ma solo di un metodo che
effettui la validazione.
Come usiamo il nostro attributo nuovo di zecca? Beh, ad esempio...
public class Person
{
private string firstName;
[Required()]
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
// .... codice .....
public void Validate()
{
foreach(PropertyInfo property in this.GetType().GetProperties())
{
RequiredAttribute attr = property.GetCustomAttribute(
property, typeof(RequiredAttribute));
if (attr != null && !attr.IsValid(property, this))
throw new Exception(string.Format(
"Errore: la proprietà {0} non può essere vuota!",
property.Name));
}
}
}
Nel metodo Validate(), tramite reflection, si cicla per
tutte le proprietà che appartengono alla classe Person. Nel caso in cui queste
siano decorate da un RequiredAttribute, se ne richiama il metodo
IsValid per verificarne la correttezza.
Ora, sono d'accordo che quanto scritto sino ad ora possa
sembrare un'inutile complicazione, ce la saremmo potuta benissimo cavare con un
if (FirstName == null || FirstName.Length == 0) then
throw new Exception(...);
Qual è allora il vantaggio di quest'approccio
dichiarativo? Beh, innanzitutto si può implementare il metodo Validate in una
classe base da cui ereditano tutti gli oggetti del Domain Model, in modo da
poter verificare la corretta valorizzazione delle proprietà obbligatorie
semplicemente decorandole con [Required()]. Inoltre nulla vieta di realizzare custom attributes di
validazione più complessi, che verifichino ad esempio che il valore di una
proprietà cada entro un certo intervallo, che soddisfi una certa regular
expression o addirittura demandarne la validazione ad una porzione
di codice custom. Se è interessante, magari riprenderò il discorso in un
prossimo post.
Per chi volesse comunque approfondire, segnalo questa pagina della MSDN Library e quest'articolo di Ted
Pattison su MSDN Magazine.