DCSIMG
Learning Validation Application Block with Strippers: Using Bussiness Entities with Validator Attributes - Justin myJustin = new Justin( Expriences.Current );

Learning Validation Application Block with Strippers: Using Bussiness Entities with Validator Attributes

סדרת לומדים Validation Application Block עם חשפניות

1. קדם דבר: למה צריך וולידציה? (או: "קופים. פשוט קופים."), פורסם ב-26.6.2007

2. חלק ראשון: הפתרון הקיים והמצוי ב-Winforms, Console ו-ASP.Net (או: "איך קוראים לך ובת כמה את?"), פורסם ב-8.7.2007

3. חלק שני: שימוש ישיר ב-Validatorים של VAB (או: "איך לגרום לקוד יחסית קריא להפוך להיות ארוך ומעיק"), יפורסם ב-9.7.2007

4. חלק שלישי: הכנסת נתוני הוולידטורים לתוך מחלקות (או: "ממלכתי עבור וולידטור ג'נארי"), פורסם ב-10.7.2007

5. חלק רביעי: אינטגרציה בסיסית עם ה-GUI ו-וולידציה מתוך קבצי קונפיוגרציה (או: "קצת יותר Drag&Drop, קצת פחות קוד"), יפורסם ב-11.7.2007

6. המשך יבוא.  

image

 

חלק שלישי: הכנסת נתוני הוולידטורים לתוך מחלקות (או: "ממלכתי עבור וולידטור ג'נארי")

נוסיף מחלקה חדשה. (גם באנגלית: Class)

 

ניצור פרוייקט חדש מסוג Class Library ונקרא לו BL:

image_thumb441_thumb2

נוסיף מחלקה חדשה בשם Stripper ואת ה-refernces של VAB. (בנוסף, נמחוק את ה-references של VAB משאר הפרוייקטים עד כה)

image_thumb461_thumb2

נדאג להוסיף references לפרוייקט הזה משאר הפרוייקטים.

image_thumb481_thumb3

image_thumb50_thumb2

נדאג לבצע Add reference מכל פרוייקט אחר שלנו (Console, Winforms ו-ASP.Net) לפרוייקט שנקרא BL וכרגע ריק יחסית.

נוסיף שני מאפיינים (גם באנגלית: Properties) למחלקת החשפנית שלנו.
אחד שייצג את שם החשפנית והשני שייצג את גיל החשפנית.
בנוסף, גם נוסיף קונסטרקטור (גם באנגלית: Constructor) שמקבל את שני הפרמטרים הנ"ל. 

namespace BL

{

    public class Stripper

    {

        public Stripper()

        {

        }

 

        public Stripper(string Name, int Age)

        {

            this.Name = Name;

            this.Age = Age;

        }

 

        private string _name;

        public string Name

        {

            get { return _name; }

            set { _name = value; }

        }

 

        private int _age;

        public int Age

        {

            get { return _age; }

            set { _age = value; }

        }

    }

}

בנקודה זו אני רוצה לשלב תמונה של חשפנית כי כבר הרבה מאוד קוד לא ראינו אישה חצי עירומה.

image_thumb53_thumb2

נמשיך.

עכשיו ניקח את הקוד הבא שהוא הקוד המעודכן של ה-Console שלנו ונחליף אותו ככה שיעבוד עם אותו מחלקהת Stripper שכרגע יצרנו.

            Console.WriteLine("*** adding new stripper ***");

 

            Console.Write("Please enter stripper's name: ");

            string StripperName = Console.ReadLine();

 

            StringLengthValidator notNullValidator = new StringLengthValidator(1, 100);

            ValidationResults stripperNameResults = notNullValidator.Validate(StripperName);

            if (!stripperNameResults.IsValid)

                Console.WriteLine("no stripper name supplied");

 

יהפוך ל-

            Console.WriteLine("*** adding new stripper ***");

            Stripper newStripper = new Stripper();

 

            Console.Write("Please enter stripper's name: ");

            newStripper.Name = Console.ReadLine();

 

            StringLengthValidator notNullValidator = new StringLengthValidator(1, 100);

            ValidationResults stripperNameResults = notNullValidator.Validate(newStripper.Name);

            if (!stripperNameResults.IsValid)

                Console.WriteLine("no stripper name supplied");

וכנ"ל נשנה גם את הקוד של גיל החשפנית שיכנס ל-Stripper.Age.
מה שכן, נכון לעכשיו בשלב זה של המאמר - נפסיק לבדוק אם הקלט הוא אכן מספר. נחזור לבדוק את זה בשלבים הרבה יותר מאוחרים של המאמר שקשורים לאינטגרציה (סוג של ממתק סובייטי) עם ה-GUI (שזה סוג של כלב הולדני).

הקוד הבא אחרי שנניח כי הקלט הוא תמיד מספר יהפוך מ-

            Console.Write("Please enter stripper's age: ");

            string StripperInputAge = Console.ReadLine();

 

 

            TypeConversionValidator stringConversionValidator = new TypeConversionValidator(typeof(int));

            RangeValidator moreThen18Validator = new RangeValidator(18, int.MaxValue);

            if (stringConversionValidator.Validate(StripperInputAge).IsValid)

            {

                if (!moreThen18Validator.Validate(Convert.ToInt32(StripperInputAge)).IsValid)

                    Console.WriteLine("stripper is not 18 years old");

            }

            else

            {  

                Console.WriteLine("stripper's age must be a valid number");

            }

לקוד הבא:

            Console.Write("Please enter stripper's age: ");

            string StripperInputAge = Console.ReadLine();

 

            RangeValidator moreThen18Validator = new RangeValidator(18, int.MaxValue);

            if (!moreThen18Validator.Validate(Convert.ToInt32(StripperInputAge)).IsValid)

                Console.WriteLine("stripper is not 18 years old");

ובשימוש עם המחלקת Stripper שלנו נקבל:

            Console.Write("Please enter stripper's age: ");

            newStripper.Age = Convert.ToInt32(Console.ReadLine());

 

            RangeValidator moreThen18Validator = new RangeValidator(18, int.MaxValue);

            if (!moreThen18Validator.Validate(newStripper.Age).IsValid)

                Console.WriteLine("stripper is not 18 years old");

 

אז למה התחלנו לעבוד עם מחלקה? הרי לא חסכנו כאן שום קוד!

אז בואו נראה אחד מהפיצ'רים האדירים של VAB, נכתוב בתוך המחלקה שלנו איזה וולידציה מתבצעת עליה.

אמרנו הרי שאת Name אנחנו רוצים לבדוק שהוא לפחות באורך תו 1 ולא יותר מ-100 תווים, ואת Age אנחנו רוצים לבדוק שהוא בין 18 ל-int.MaxValue.
בואו פשוט נכתוב את זה בקוד שלנו של המחלקה!!!

namespace BL

{

    public class Stripper

    {

        public Stripper()

        {

        }

 

        public Stripper(string Name, int Age)

        {

            this.Name = Name;

            this.Age = Age;

        }

 

        private string _name;

        [StringLengthValidator(1, 100)]

        public string Name

        {

            get { return _name; }

            set { _name = value; }

        }

 

        private int _age;

        [RangeValidator(18, RangeBoundaryType.Inclusive, int.MaxValue,  RangeBoundaryType.Inclusive)]

        public int Age

        {

            get { return _age; }

            set { _age = value; }

        }

    }

}

אמרנו ככה: Name הוא בין 1 ל-100 תווים, ו-Age הוא מספר בין 18 ל-int.MaxValue ("כולל" 8 ו-int.MaxValue ולכן ציינו Inclusive).

עכשיו נעשה משהו קיצוני לקוד של ה-Console. הקוד הבא:

            Console.WriteLine("*** adding new stripper ***");

            Stripper newStripper = new Stripper();

 

            Console.Write("Please enter stripper's name: ");

            newStripper.Name = Console.ReadLine();

 

            StringLengthValidator notNullValidator = new StringLengthValidator(1, 100);

            ValidationResults stripperNameResults = notNullValidator.Validate(newStripper.Name);

            if (!stripperNameResults.IsValid)

                Console.WriteLine("no stripper name supplied");

 

 

 

            Console.Write("Please enter stripper's age: ");

            newStripper.Age = Convert.ToInt32(Console.ReadLine());

 

            RangeValidator moreThen18Validator = new RangeValidator(18, int.MaxValue);

            if (!moreThen18Validator.Validate(newStripper.Age).IsValid)

                Console.WriteLine("stripper is not 18 years old");

יהפוך ל:

            Console.Write("Please enter stripper's name: ");

            newStripper.Name = Console.ReadLine();

            Console.Write("Please enter stripper's age: ");

            newStripper.Age = Convert.ToInt32(Console.ReadLine());

 

            Validator<Stripper> stripperValidator = ValidationFactory.CreateValidator<Stripper>();

            ValidationResults resultsForCurrentStripperValidation = stripperValidator.Validate(newStripper);

 

            foreach (ValidationResult resultToPrint in resultsForCurrentStripperValidation)

            {

                Console.WriteLine("{0} - {1}", resultToPrint.Key,  resultToPrint.Message);

            }

נעבור על זה שורה-שורה.

            Console.Write("Please enter stripper's name: ");

            newStripper.Name = Console.ReadLine();

            Console.Write("Please enter stripper's age: ");

            newStripper.Age = Convert.ToInt32(Console.ReadLine());

העפנו את כל אתחול ה-Validatorים הפרימיטיביים מהקוד שלנו. הרי הם כבר כתובים במחלקה.
אז כל מה שנשאר זה לאתחל את מחלקת ה-Stripper שלנו.

            Validator<Stripper> stripperValidator = ValidationFactory.CreateValidator<Stripper>();

יצרנו מופע של משהו שנקרא <Validator<Stripper. קראנו שם לאיזה ValidationFactory.CreateValidator שמקבל כפרמטר ג'נארי את ה-T שאנחנו רוצים לקבל את ה-Validator שלו.

נחשוב ביחד על המשמעות של זה - הרי עכשיו למעשה ה-Stripper שלנו מכילה את כל הוולידטורים שלנו והיא בעצמה וולידטור!

            ValidationResults resultsForCurrentStripperValidation = stripperValidator.Validate(newStripper);

 אם אמרנו שעכשיו יש לנו Validator עם כל המידע שיושב על Stripper הרי מספיק לנו לשלוח לוולידטור הזה מופע (גם באנגלית: Instance) של Stripper והוא כבר יגיד לנו מה לא בסדר איתו.

קיבלנו בחזרה ValidationResults שזה אוסף (גם באנגלית: Collection) של כל כשלון בביצוע הוולידציה.

אם זה אוסף אז אפשר לבצע על זה foreach? בטח שאפשר.

            foreach (ValidationResult resultToPrint in resultsForCurrentStripperValidation)

            {

                Console.WriteLine("{0} - {1}", resultToPrint.Key,  resultToPrint.Message);

            }

עברנו על התוצאות והדפסנו משהו. לא ברור מה בדיוק הדפסנו ומה הולך כאן עם ההדפסה, אבל נריץ ונראה איך כל זה רץ.

image_thumb551_thumb2

נראה את זה מולנו:

*** adding new stripper ***
Please enter stripper's name:
Please enter stripper's age: 16
Name - The length of the value must fall within the range "1" (Inclusive) - "100 " (Inclusive).
Age - The value must fall within the range "18" (Inclusive) - "2147483647" (Inclusive).

 

 קיבלנו שיש בעיה עם הוולידציה של Name כי השם צריך להיות בין אות 1 ל-100 אותיות.
וגם קיבלנו שיש בעיה עם הוולידציה של Age כי הערך צריך להיות בין 18 ל-int.MaxValue.

נשנה את ההודעות שגיאה האלו למשהו קצת יותר ידידותי.
אם הוולידטורים יושבים על Stripper אז נשנה את Stripper כך שהודעות השגיאה יופיעו גם בו. הקוד הבא של Stripper יהפוך מ-

namespace BL

{

    public class Stripper

    {

     ...

 

        private string _name;

        [StringLengthValidator(1, 100)]

        public string Name

        {

            get { return _name; }

            set { _name = value; }

        }

 

        private int _age;

        [RangeValidator(18, RangeBoundaryType.Inclusive, int.MaxValue, RangeBoundaryType.Inclusive")]

        public int Age

        {

            get { return _age; }

            set { _age = value; }

        }

    }

}

יהפוך ל- 

namespace BL

{

    public class Stripper

    {

        ...

 

        private string _name;

        [StringLengthValidator(1, 100,

            MessageTemplate = "Stripper name is between 1 to 100 charcters")]

        public string Name

        {

            get { return _name; }

            set { _name = value; }

        }

 

        private int _age;

        [RangeValidator(18, RangeBoundaryType.Inclusive, int.MaxValue,  RangeBoundaryType.Inclusive,

                        MessageTemplate="Stripper has to be at least 18")]

        public int Age

        {

            get { return _age; }

            set { _age = value; }

        }

    }

}

נראה את זה בהרצה:

image_thumb58_thumb2

ניקח את הקוד שכתבנו עד כה ב-Console ונעתיק אותו לתוך Winforms ו-ASP.Net. הקוד הנוכחי שלנו ב-Console יהפוך מ-

            Console.WriteLine("*** adding new stripper ***");

            Stripper newStripper = new Stripper();

 

            Console.Write("Please enter stripper's name: ");

            newStripper.Name = Console.ReadLine();

            Console.Write("Please enter stripper's age: ");

            newStripper.Age = Convert.ToInt32(Console.ReadLine());

 

            Validator<Stripper> stripperValidator = ValidationFactory.CreateValidator<Stripper>();

            ValidationResults resultsForCurrentStripperValidation = stripperValidator.Validate(newStripper);

 

            foreach (ValidationResult resultToPrint in resultsForCurrentStripperValidation)

            {

                Console.WriteLine(resultToPrint.Message);

            }

לקוד הבא ב-Winforms ו-ASP.Net:

        private void btnAddNewStripper_Click(object sender, EventArgs e)

        {

            Stripper newStripper = new Stripper();

 

            newStripper.Name = tbxName.Text;

            newStripper.Age = Convert.ToInt32(tbxAge.Text);

 

            Validator<Stripper> stripperValidator = ValidationFactory.CreateValidator<Stripper>();

            ValidationResults resultsForCurrentStripperValidation = stripperValidator.Validate(newStripper);

 

            foreach (ValidationResult resultToPrint in resultsForCurrentStripperValidation)

            {

                lblErrors.Text += resultToPrint.Message;

            }

        }

ובהרצה של Winforms:

image_thumb60_thumb2

ובהרצה של ASP.Net:

image_thumb62_thumb2

 

עדיין יש לי בעיה עם הקוד הזה:

            Validator<Stripper> stripperValidator = ValidationFactory.CreateValidator<Stripper>();

            ValidationResults resultsForCurrentStripperValidation = stripperValidator.Validate(newStripper);

הוא חוזר על עצמו ולפי דעתי מיותר לגמרי.

נחליף אותו בקוד הבא:

            ValidationResults resultsForCurrentStripperValidation = Validation.Validate(newStripper);

החבר'ה הטובים ברדמונד חשבו כבר על זה שאנחנו לא רוצים להיות אלו שמאתחלים כל מיני וולידטורים ובאמצעות Validation.Validate העלימו אותם לגמרי.

כל מה שאנחנו עושים בנקודה הזו זה להוסיף את הוולידטורים על המחלקה ולשלוח אותו לאיזה מתודת Validation.Validate קסומה שמחזירה לנו ValidationResults עם כל מה שאנחנו צריכים. (אגב, מאחורי הקלעים כל מה שמתבצע שם זה זה קריאה ל-ValidationFactory עבורנו + Caching זה ה-Validatorים).

 

אז עכשיו ככה נראה הקוד שלנו ב-Winforms וב-ASP.Net עם VAB:

        protected void btnAddNewStripper_Click(object sender, EventArgs e)

        {

            Stripper newStripper = new Stripper();

 

            newStripper.Name = tbxName.Text;

            newStripper.Age = Convert.ToInt32(tbxAge.Text);

 

            ValidationResults resultsForCurrentStripperValidation = Validation.Validate(newStripper);

 

            foreach (ValidationResult resultToPrint in resultsForCurrentStripperValidation)

            {

                lblErrors.Text += resultToPrint.Message;

            }

        }

וככה הוא נראה עם וולידציה ישר בתוכו:

        private void btnAddNewStripper_Click(object sender, EventArgs e)

        {

            if (string.IsNullOrEmpty(tbxName.Text))

                lblErrors.Text += "חובה להוסיף שם חשפנית. ";

 

            int stripperAge = 0;

            if (int.TryParse(tbxAge.Text, out stripperAge))

            {

                if (stripperAge < 18)

                   lblErrors.Text += "גיל חשפנית לפחות 18.";

            }

            else

            {

                lblErrors.Text += "גיל חשפנית חייב להיות מספר.";

            }

        }

 

וככה נראה הקוד שלנו ב-Console עם VAB כרגע:

            Console.WriteLine("*** adding new stripper ***");

            Stripper newStripper = new Stripper();

 

            Console.Write("Please enter stripper's name: ");

            newStripper.Name = Console.ReadLine();

            Console.Write("Please enter stripper's age: ");

            newStripper.Age = Convert.ToInt32(Console.ReadLine());

 

            ValidationResults resultsForCurrentStripperValidation = Validation.Validate(newStripper);

 

            foreach (ValidationResult resultToPrint in resultsForCurrentStripperValidation)

            {

                Console.WriteLine(resultToPrint.Message);

            }

במקום הקוד המקורי ב-Console:

            Console.Write("Please enter stripper's name: ");

            string StripperName = Console.ReadLine();

 

            if (string.IsNullOrEmpty(StripperName))

                Console.WriteLine("no stripper name supplied");

 

            Console.Write("Please enter stripper's age: ");

            string StripperInputAge = Console.ReadLine();

 

            int stripperAge = 0;

            if (int.TryParse(StripperInputAge, out stripperAge))

            {

                if (stripperAge < 18)

                    Console.WriteLine("stripper is not 18 years old");

            }

            else

            {  

                Console.WriteLine("stripper's age must be a valid number");

            }

אפשר לראות שה-GUIים השונים שלנו כבר לא מכירים יותר את כללי הוולידציה, אלא רק יודעים לעבוד עם איזה מנגנון כללי שהוא זה שאחראי לביצוע הוולידציה בפועל.

אז כבר אנחנו לא כותבים יותר קוד שבודק את הקלט של המשתמש.
עכשיו אנחנו:

1. אומרים ל-VAB מה לבדוק (בצורה של Attributes על Properties של המחלקה)

2.  שולחים ל-VAB מחלקות לבדיקה

3. מתייחסים לתוצאות של הבדיקה.

 

מה שכן מוזר לי מאוד שאנחנו נאלצים ב-Winforms וב-ASP.Net לדאוג להציג את התוצאות של הוולידציה בעצמנו.
הרי מדובר גם במשימה מאוד פשוטה ונפוצה יחסית.
הרי תחת ההנחה שכל שדה ב-GUI מתכתב למאפיין במחלקה שלנו אפשר בקלות מאוד לבצע וולידציה פר-שדה.

כלומר, עכשיו אנחנו צריכים לנסות לראות איך נוכל להוריד את הלולאת foreach על כל התוצאות שלנו.

Comments

No Comments