Technology Experience

Contenuti gestiti da Igor Damiani
posts - 949, comments - 2741, trackbacks - 15120

My Links

News

  • Questo blog si propone di raccogliere riflessioni, teoriche e pratiche, su tutto quello che riguarda il world-computing che mi sta attorno: programmazione in .NET, software attuale e futuro, notizie provenienti dal web, tecnologia in generale, open-source.

    L'idea è quella di lasciare una sorta di patrimonio personale, una raccolta di idee che un giorno potrebbe farmi sorridere, al pensiero di dov'ero e cosa stavo facendo.

    10/05/2005,
    Milano

Archives

Post Categories

Generale

Ancora sulla Janky.Validation: usarla nei progetti Windows Forms

Oggi sono rimasto off-line tutto il tempo, una vera tortura per qualsiasi bloggatore come me, e come tutti voi. Questa sera voglio recuperare, proponendo il mio metodo di validazione che ho deciso di adottare nel mio software di fatturazione. Il progetto è ovviamente di tipo Windows Forms. Mi interessava la Janky.Validation, perchè ne ho discusso con Giancarlo che me ne ha sempre parlato bene, ed effettivamente da quando mi ha passato i sorgenti, ho cercato di usarla con regolarità. Il risultato ottenuto è davvero ottimo, e sono qui a parlarvi proprio di questo.

Non voglio scendere troppo nel dettaglio, perciò arrivo al dunque.
Per adesso, basta sapere che sul click di ogni pulsante Salva istanzio un oggetto ValidationContext ed aggiungo tutte le RuleBase necessarie alla validazione. Se la validazione ha successo, allora procedo con il salvataggio dell'oggetto. Altrimenti gestisco le BrokenRules che, come descritto da Giancarlo (Janky) Sudano nel suo post, ritornano essenzialmente le regole non validate: per ogni BrokenRule infranta, ho un object (che rappresenta il controllo) ed una string (che rappresenta il messaggio di errore) che uso per piegare ai miei voleri l'ErrorProvider.
Ma c'era comunque qualcosa che volevo migliorare.

Non voglio referenziare la Janky.Validation nella UI
Facendo così, la validazione avviene nel layer UI. In questo layer, ho referenziato la Janky.Validation. Sul click ho istanziato un ValidationContext. Questa cosa non mi piaceva molto. Io volevo raggiungere i seguenti obiettivi:

  1. Slegare il più possibile la UI dal meccanismo di validazione. Oggi uso la Janky.Validation, cosa succede se un domani uso il Validation Block? Oppure altro? Io nella UI non voglio saperne di librerie esterne, voglio un qualcosa che nel modo più ".NET possibile" mi dica se ci sono errori oppure no.
  2. Passo seguente: voglio un modo più "intelligente" per capire a quale controllo fa riferimento ogni errore. Non mi piace usare l'enum, perchè sono costretto a fare uno switch per ogni BrokenRules, e dedurre da questo il Control. Non è per niente bello, su, da bravi, ripetetelo con me: non è bello, non è bello, non è bello.
  3. Passo seguente: ho costruito una classe che mi istanzia un oggetto xyzValidator, dove xyz sta per un oggetto del mio domain model. Quindi, avrò un ArticoloValidator, un FatturaValidator, un ClienteValidator, etc.
  4. Questa classe accede al meccanismo sottostante esposto dalla Janky.Validation. Essa non fa nient'altro che ritornarmi un IDictionary<object, string>, che mappa 1:1 le BrokenRules infrante. Stessa cosa di prima, per il resto: object contiene il controllo per questo errore, mentre string è il messaggio d'errore.
  5. Sul click del pulsante Salva, adesso, istanzio questa classe wrapper. Gestisco l'IDictionary di ritorno e manipolo l'ErrorProvider visualizzando eventuali errori.

Tutto questo è convertito in righe di codice. Eccovele. Innanzitutto ho dichiarato una classe Validator astratta.

public abstract class Validator
{
    
protected Dictionary<objectstring> _dict = new Dictionary<objectstring>();
    
protected ValidationContext ctx;
    
protected bool initialized = false;
    
protected object[] _controls;

    
protected abstract void Initialize();

    
public Validator()
    { ctx = 
new ValidationContext(); }

    
public object[] Controls
    {
        
get return _controls; }
        
set { _controls = value; }
    }

    
public Dictionary<objectstring> IsValid()
    {
        
if (!initialized) Initialize();

        
if (!ctx.IsValid())
        {
            
foreach (RuleBase rb in ctx.BrokenRules)
                _dict.Add(rb.Key, rb.Message);
        }
        
return (_dict);
    }

La classe Validator contiene una proprietà _dict, che verrà ritornata al chiamante. Contiene un ValidationContext, che conterrà tutte le RuleBase. Contiene un bool di utility che mi serve per capire quando il Validator è a posto. Poi c'è una cosa che devo migliorare: l'array di controlli, a cui ogni possibile errore fa riferimento. Il metodo Initialize è astratto, perchè va specializzato dalla classe reale. Essendo la Validator astratta, ho creato 3 classi Validator reali (Cliente, Articolo e Fattura). Ne faccio vedere solo uno, quello dell'articolo.

public class ArticoloValidator : Validator
{
    
private Articolo _articolo;

    
public ArticoloValidator(Articolo Articolo, object[] UIControls)
    {
        _articolo = Articolo;
        _controls = UIControls;
    }

    
protected override void Initialize()
    {
        ctx = 
new ValidationContext();
        ctx.Rules.Add(
new IsValidStringLengthRule(_controls[0], _articolo.Codice, 1, 10, "Inserire un codice da 1 a 10 caratteri!"));
        ctx.Rules.Add(
new IsValidStringLengthRule(_controls[1], _articolo.Denominazione, 1, 255, "La denominazione è obbligatoria!"));
        ctx.Rules.Add(
new IsValueInRangeRule(_controls[2], (double)_articolo.CostoUnitario, 1, 5000, "Il costo unitario è obbligatorio!"));
        ctx.Rules.Add(
new IsValueInRangeRule(_controls[3], (double)_articolo.Iva, 20, 20, "Secondo le leggi in vigore, l'IVA è sempre al 20%!"));
    }
}

Il costruttore di ArticoloValidator prende in ingresso un'istanza di Articolo ed un array di controlli. L'override di Initialize(), veramente brutta così com'è, riempie il ValidationContext definito nella classe base, aggiunge le RuleBase specifiche che mi servono ed il gioco è fatto. Il risultato è che adesso quando chiamo il metodo IsValid della classe Validator, mi viene ritornato un IDictionary<object, string>, come dicevo prima.

Nel layer UI, per ogni WF ho creato una function che ritorna un bool.

private bool notifyErrors()
{
    errNotifica.Clear();
    ArticoloValidator validator = 
new ArticoloValidator(currentEntity,
        
new object[] { txtCodice, txtDenominazione, txtCostoUnitario, txtIva });
    
    IDictionary<
objectstring> dict = validator.IsValid();

    
if (dict.Count == 0) return (false);
    IEnumerator<KeyValuePair<
objectstring>> cycle = dict.GetEnumerator();
    
while (cycle.MoveNext())
    {
        
if (cycle.Current.Key != null)
        {
            errNotifica.SetIconAlignment((Control)cycle.Current.Key, ErrorIconAlignment.TopLeft);
            errNotifica.SetError((Control)cycle.Current.Key, cycle.Current.Value);
        }
    }
    
return(true);
}

Questa function ritorna true se ci sono uno o più errori. Ritorna false se invece è tutto ok. Notare che il costruttore riceve un oggetto currentEntity (di tipo Articolo) ed un array di Controls.
Da notare inoltre che nel blocco di codice riportato qui sopra non si parla assolutamente di Janky.Validation. Infatti, tale libreria non è assolutamente referenziata nella UI. Non ci sono istruzioni using nel codice della WF. E' tutto molto pulito. Non faccio altro che chiedere alla classe ArticoloValidator un qualche feedback sulla validazione, che mi viene ritornato da un IDictionary. Se un giorno volessi cambiare metodo di validazione, modificherei l'implementazione di ArticoloValidator, e niente di più.

Ultimo appunto, ma che reputo importante. Dopo aver ottenuto un IEnumerator, ciclo tranquillamente l'IDictionary. Sono sicuro che la Key contiene il Control, e il Value contiene il messaggio di errore. Si può migliorare il casting, si possono migliorare un po' di cose, ma per adesso i miei obiettivi li ho raggiunti. Evviva!

powered by IMHO 1.3

Print | posted on Monday, March 27, 2006 10:38 PM | Filed Under [ Tecnologia ]

Feedback

Gravatar

# re: Ancora sulla Janky.Validation: usarla nei progetti Windows Forms

Ma in janky.Validation la classe ValidationContext è abstract, quindi nella Initialize di ArtivcoloValidator andrebbe

ctx = new ArticoloValidationContext()

3/28/2006 11:04 AM | Tommaso Caldarola
Gravatar

# re: Ancora sulla Janky.Validation: usarla nei progetti Windows Forms

Tommaso:
hai ragione, io uso una release precedente, dove la ValidationContext non è abstract. Ho appena scritto a Janky per poter avere l'ultima release della libreria.
Ma pensa te... :-)
3/28/2006 11:25 AM | Igor Damiani
Gravatar

# re: Ancora sulla Janky.Validation: usarla nei progetti Windows Forms

Comunque questa soluzione non copre il caso in cui si vuole utilizzare una DataGridView con un error provider info a patto di creare tanti validator elementari (uno per property che si intende validare) richiamabili singolarmente nel indicizzatore string IDataErrorInfo.this[string columnName] più un validator che li raggruppa tutti.
3/28/2006 5:43 PM | Tommaso Caldarola
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET