Cloud First, Mobile First. Questo è il mantra che Satya Nadella ha inculcato nel cambio di pelle e di passo di Microsoft, questo mantra per noi developer si è materializzato attraverso .Net Core, framework cross-platform  back – end, il sempre verde .Net Framework e l’acquisizione di Xamarin per le app mobile cross-platform, tutto molto bello se non fosse per il fatto che ognuno di questi 3 Framework è assolutamente indipendente e non interscambili. Non sempre con le librerie scritte in .Net Framework è possibile fare il porting indolore verse un’altro framework.

dotnet-today

Per aumentare la produttività ed il riutilizzo del codice Microsoft ha iniziato un progetto molto ambizioso: .Net Standard, un framework class library universale con l’obbiettivo di unificare le Base Class Library, le Core Library e Mono Class Library introducendo un punto di astrazione che solo in fase di compilazione per ogni specifica piattaforma di destinazione viene aggiunta la specifica implementazione, naturalmente legata alla piattaforma di compilazione.

Diciamo subito a scanso di equivoci che le .Net Standand saranno un set ristretto delle api disponibili nel .Net Framework in quanto non tutte le Api possono essere convertite in cross platform in quanto fortemente accoppiate con i Sistemi Operativi di casa Redmond.

dotnet-tomorrow

Questo è il sogno di noi “dev” poter riutilizzare su qualsiasi piattaforma la maggior parte del codice personalizzando solo la parte di presentazione.

Diamo adesso un occhiata sotto al cofano per vedere come questa cosa sia possibile e come Microsoft ha affrontato e risolto i problemi di compatibilità tra varie piattaforme e device di destinazione.

netstandard-refs-tomorrow (1)

La .Net Standard propone una serie di Api che in fase di compilazione a seconda della device e piattaforma selezionata carica l’implementazione opportuna.

Chiaramente questo strato di astrazione porta con se inevitabilmente dei breaking change, chiaramente il .Net Framework avrà le ricadute maggiori in merito ai breaking change, cosa del tutto comprensibile a fronte dell’obbiettivo che il progetto .Net Standard si propone, quindi tutte le Api specifiche dei sistemi operativi di casa Redmond, come ad esempio Microsoft.Win32.Registry, non saranno supportate.

netstandard-apis (1)

Per maggiori informazioni sullo sta dell’arte del progetto vi rimando alla documentazione ufficiale al sito Documentazione .Net Standard  e al repository   GitHub - .Net Standard.



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

Calendar

<<  May 2017  >>
MonTueWedThuFriSatSun
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar

Category list

Widget Tag cloud not found.

Sequence contains no elementsX