In questo primo post vorrei parlarvi di un mondo fantastico i Generic ed i Delegate, l’intento non è assolutamente quello di essere completamente esaustivo su entrambi gli argomenti ma partendo da una situazione reale immaginare insieme come è possibile, con l’utilizzo di questi due utilissimi strumenti, creare classi altamente riutilizzabili e con un alto grado di manutenibilità.

Lo spunto per questo post arriva da due semplici domande:

Quante volte ci si è persi all'interno di interminabili e complesse procedure di validazione per aggiungere o togliere un controllo?
Quanto tempo si è dedicato al loro refactoring?

La validazione dell'input è tra le maggiori criticità di ogni sistema minimamente complesso, il .Net mette a disposizione una serie di funzionalità base ed avanzate quali DataAnnotations e dintorni che non sono niente male, rimane comunque il problema di gestire i controlli custom, cosa non proprio banale.

Partendo da queste considerazioni propongo un approccio smart e generico che integri le validazioni della DataAnnotations  e le estende con la possibilità di aggiungere delle funzioni complesse di controllo del modello.

esempio:

    class modelinput
    {
        [Required]
        public int Id { get; set; }
        [StringLength (20)]
        public string PropertyA { get; set; }
        [StringLength(50, MinimumLength = 10, ErrorMessage = "Error: Min 10 and Max 50 characters") ]
        public string PropertyB { get; set; }
        public List<int> CollectionProperty { get; set; }
    }

    class Main
    {
        public void InsertData(modelinput item)
        {
            SmartValidator.Validator.Create();
            SmartValidator.Validator.AddValidation<modelinput>(o => (o.PropertyA == "Test" && o.CollectionProperty.Count <= 10 && o.CollectionProperty.Any( e => e == 5) ) || o.PropertyB == "Demo", "Max 10 Elements");

            if (SmartValidator.Validator.Validate<modelinput>(item).IsValid)
            {
                // add here logic

            }
        }
    }
  

Nel codice sopra la SmartValidator si fa carico di verificare se le proprietà della classe rispettano le regole di validazione della DataAnnotions ed effettua un controllo custom ( se la proprietà PropertyA è valorizzata con Test e la lista CollectionProperty ha meno di 10 elementi ed almeno uno di loro è uguale 5 oppure la proprietà PropertyB è valorizzata con Demo).

Tutto questo chiaramente può essere integrato nella DataAnnotations creando un CustomValidator etc etc, forse questo può essere un approccio più Smart.

Questo è il caso d’uso di partenza adesso passiamo ad analizzare la SmartValidator e capire come è possibile implementarla con pochissime righe di code grazie all’ausilio dei Generic e Delegate.

La Classe Statica Validor:

 public static class Validator

    {
        private static Dictionary<Type,object > dic;

        public static void Create()
        {
            dic = new Dictionary<Type  ,object >();
        }

        public static void AddValidation<TObject>(Func<TObject , bool> validation,string ErrorMessage)
        {
            if (dic == null)
                throw new Exception("Smart Validator Not initialize");
            if ( dic.ContainsKey(typeof( TObject) ))
            {
              ((ValidatorGeneric<TObject>)  dic[typeof(TObject)]).Add(validation,ErrorMessage );
            }
            else
            {
                dic.Add(typeof(TObject), new ValidatorGeneric<TObject>( validation,ErrorMessage  ));
            }

        }

        public static ValidationResult Validate<TObject>(TObject Item)
        {
            if (dic == null)
                throw new Exception("Smart Validator Not initialize");
            return dic.ContainsKey(typeof(TObject)) ? ((ValidatorGeneric<TObject>)dic[typeof(TObject)]).Validate(Item) : new ValidatorGeneric<TObject>().ValidateOnlyDataAnnotations(Item);
        }

    }
  
 public class ValidationResult
    {
        public ValidationResult()
        {
            IsValid = true;
            ErrorMessage = "";
            NumberOfError = 0;
        }
        public bool IsValid { get; set; }
        public string ErrorMessage { get; set; }
        public short NumberOfError { get; set; }
    }
  

La struttura Dictionary è perfetta per gestire più modelli da validare associando ad ogni modello un ValidatorGeneric che analizzo nel dettagli più avanti. Quanto viene invocato il metodo Validate se non esiste una chiave associata al modello nel dictionary crea un ValidatorGeneric ed effettua la sola validazione della DataAnnotations, in entrambi i casi il risultato della validazione è un’istanza di ValidationResult.

Nel metodo AddValidation troviamo la combinazione Generic e Delegate come parametri, concentriamoci per primo sul Delegate Func<>. 
I delegate sono un promessa di implementazione, che l’utilizzatore deve effettuare, a runtime viene unita al resto del codice, questo ci permette di riutilizzare il codice, implementando di volta in volta solo alcune parti variabili, che appunto sono definite dal Delegate.

Il Func<> è un particolare tipo di Delegate che definisce una firma specifica dell’implementazione ove i primi n-1 parametri rappresentano i tipi di input l’ultimo il tipo di ritorno.

Esempio:

Func<int,bool> -> bool MyMethod(int input)

Affrontiamo adesso la parte Generic del metodo, i Generic rappresentano la possibilità di creare metodo, classi e più in generale codice, ove il tipo dati non è definito e varierà, nell’esempio il Generic TObject viene utilizzato per valorizzare il Dictionary e come parametro di input del delegate Func<> sopra trattato.

Passiamo adesso all’utilizzo della combinazione Generic e Func<> per definire la pipiline di validazione.

Classe ValidatorGeneric:

    class ValidatorGenericItem<TObject>
    {
        public ValidatorGenericItem(Func<TObject, bool> validation, string ErrorMessage)
        {
            this.validation = validation;
            this.ErrorMessage = ErrorMessage;
        }
        public Func<TObject, bool> validation;
        public string ErrorMessage;
    }
  
    class ValidatorGeneric<TObject>
    {
        private List<ValidatorGenericItem<TObject>> lst;
        public ValidatorGeneric()
        {
            lst = new List<ValidatorGenericItem<TObject>>();
        }
        public ValidatorGeneric(Func<TObject, bool> validation, string messageError)
        {
            lst = new List<ValidatorGenericItem<TObject>>();

            lst.Add(new SmartValidator.ValidatorGenericItem<TObject>(validation, messageError));
        }
        public void Add(Func<TObject, bool> validation, string ErrorMessage)
        {
            lst.Add(new SmartValidator.ValidatorGenericItem<TObject>(validation, ErrorMessage));
        }

        public ValidationResult Validate(TObject item)
        {
            var valid = new ValidationResult();

            foreach (var validation in lst)
            {
                if (!validation.validation(item))
                {
                    valid.ErrorMessage += (valid.ErrorMessage.Length > 0 ? " " : "") + validation.ErrorMessage;
                    valid.IsValid = false;
                    valid.NumberOfError++;
                }
            }

            ValidateDataAnnotations(item, valid);

            return valid;
        }
        private ValidationResult ValidateDataAnnotations(TObject item, ValidationResult valid)
        {
            var resultDA = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
            System.ComponentModel.DataAnnotations.Validator.TryValidateObject(item, new System.ComponentModel.DataAnnotations.ValidationContext(item), resultDA, true);


            foreach (var validationDA in resultDA)
            {
                valid.ErrorMessage += (valid.ErrorMessage.Length > 0 ? " " : "") + validationDA.ErrorMessage;
                valid.IsValid = false;
                valid.NumberOfError++;
            }
            return valid;
        }

        public ValidationResult ValidateOnlyDataAnnotations(TObject item)
        {

            return ValidateDataAnnotations(item, new ValidationResult());
        }

    }
  

La Classe ValidateGeneric<> ha una lista di Func<> dove vengono memorizzati tutte le implementazioni del delegate inserite in fase di configurazione.

Quando viene invocato il metodo validate, questo non fa altro che eseguire la scansione della lista ed invocare tutte le funzioni precedentemente caricate, nel caso la funzione ritorni false viene aggiunta la stringa di errore all’ErrorMessage ed invalida la proprietà IsValid dell’istanza della classe ValidationResult precedentemente descritta.

Quindi con l’ausilio dei generic e delegate si è riusciti a sintetizzare una procedura di validazione che rimanda all’utilizzare la definizione di tutte le possibili combinazioni di validazione e soprattutto la classe è utilizzabile con diversi modelli contemporaneamente.

Per chi fosse interessato alla solution oggetto dell’articolo potete trovarla sul mio repository GitHub al seguente link:https://github.com/AVenditti183/SmartValidation


idea

Autore:


blog comments powered by Disqus

Calendar

<<  July 2017  >>
MonTueWedThuFriSatSun
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar

Category list

Widget Tag cloud not found.

Sequence contains no elementsX