Crad's .NET Blog

L'UGIblog di Marco De Sanctis
posts - 190, comments - 457, trackbacks - 70

Un custom attribute per la validazione

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) && ((stringvalue).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.

powered by IMHO 1.3

Print | posted on lunedì 6 marzo 2006 23:34 | Filed Under [ .Net 2.0 ]

Powered by:
Powered By Subtext Powered By ASP.NET