DCSIMG
Validation Application Block with Strippers: ObjectValidator (Overview of all VAB Validators) - Justin myJustin = new Justin( Expriences.Current );

Validation Application Block with Strippers: ObjectValidator (Overview of all VAB Validators)

image

 

סדרת לומדים 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. חלק רביעי המשך: אינטגרציה בסיסית עם ה-GUI ו-וולידציה מתוך קבצי קונפיוגרציה (או: "קצת יותר Drag&Drop, קצת פחות קוד"), פורסם ב-12.7.2007

7. StringLengthValidator - בודקים שהכניסו שם חשפנית, פורסם ב-15.7.2007

8. RangeValidator - נו, תן ציון ל-Lap dance שקיבלת, פורסם ב-16.7.2007

9. DateTimeRangeValidator - מתי ביקרת אצלנו?, פורסם ב-17.7.2007

10. ContainsCharctersValidator ו-Negated - איך קוראים לך?, פורסם ב-18.7.2007

11. RelativeDateTimeValidator - מתי היום-הולדת שלך?, פורסם ב-22.7.2007

12. PropertyComparisonValidator ושני וולידטורים על אותו מאפיין - נבדוק שהמבקר נולד לפני שהוא ביקר במועדון, פורסם ב-24.7.2007

13.  RegexValidator - בודקים את הדוא"ל של המבקר , פורסם ב-26.7.2007

14. DomainValidator - מה הסוג משקה אלכוהולי האהוב עלייך?, פורסם ב-29.7.2007

15. ObjectValidator - מה המספר פלאפון שלך?, פורסם ב-31.7.2007

16. ObjectCollectionValidator - מה המספרי פלאפון של החברים שלך?, יפורסם ב-2.7.2007

 

 

ObjectValidator - מה המספר פלאפון שלך?

התקבלה ההחלטה שניקח מהמבקרים במועדון החשפנות את הפלאפון שלהם ונתקשר אליהם שבוע לפני היום-הולדת שלהם כדי להזכיר להם על ה-Lap dance החינם.
בנוסף, במהלך אותו ישיבה רבת-מעלה בצידי הבמה בזמן שהאורות האדומים והירוקים מהבהבים, נאמר משהו על כך שבעתיד נרצה לקחת עוד כמה מספרי פלאפון. ואם זה לא היה מספיק גרוע, שאנחנו שומרים למסד נתונים נרצה לשמור את הקידומת (054\052\056\050) בנפרד משאר מספר הפלאפון ובכלל לתוך טבלה נפרדת.

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

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

    public class CellPhone

    {

 

    }

מה המחלקה הזו תכיל? מספר פלאפון זה בטוח.

    public class CellPhone

    { 

        private string _phoneNumber;

 

        public string PhoneNumber

        {

            get { return _phoneNumber; }

            set { _phoneNumber = value; }

        }

    }

נוסיף גם איזה קונסטרקטור שבתוכו נוכל לאתחל את המספר פלאפון הזה.

    public class Phone

    {

        public Phone(string PhoneNumber)

        {

            this.PhoneNumber = PhoneNumber;

        }

 

        private string _phoneNumber;

 

        public string PhoneNumber

        {

            get { return _phoneNumber; }

            set { _phoneNumber = value; }

        }

    }

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

    public class CellPhone

    {

        public CellPhone(string PhoneNumber)

        {

            this.PhoneNumber = PhoneNumber;

        }

 

        private string _phoneNumber;

        [RegexValidator(@"^05[0246]([-]{0,1})\d{7,8}$",

                        MessageTemplate="Must be a valid cell phone number")]

        public string PhoneNumber

        {

            get { return _phoneNumber; }

            set { _phoneNumber = value; }

        }

    }

נעבור בקצרה על ה-Regex לדוגמה הזה:
image_thumb26  - מחייב שמספר הפלאפון יתחיל עם 05 ואחריו 0, 2, 4 או 6.
image_thumb27 - משאיר מקום לסימן מקף "-" באמצע המספר פלאפון ומאפשר בין 0 ל-1 הופעות שלו.
image_thumb28  - בין 7 ל-8 ספרות

ניצור את הפונקציה הרלוונטית שדיברנו עליה שמחלצת את הקידומת (שלושת הספרות הראשונות) של מספר הפלאפון.

    public class CellPhone

    {

        public CellPhone(string PhoneNumber)

        {

            this.PhoneNumber = PhoneNumber;

        }

 

        private string _phoneNumber;

        [RegexValidator(@"^05[0246]([-]{0,1})\d{7,8}$",

                        MessageTemplate="Must be a valid cell phone number")]

        public string PhoneNumber

        {

            get { return _phoneNumber; }

            set { _phoneNumber = value; }

        }

 

        public string ExctractCellPhoneAreaCode()

        {

            if (PhoneNumber.Length < 4)

                return null;

            else

                return PhoneNumber.Replace("-", "").Substring(0, 4);

        }

    }

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

    public class CellPhone

    {

        public CellPhone(string PhoneNumber)

        {

            this.PhoneNumber = PhoneNumber;

        }

 

        private string _phoneNumber;

        [RegexValidator(@"^05[0246]([-]{0,1})\d{7,8}$",

                        MessageTemplate="Must be a valid cell phone number")]

        public string PhoneNumber

        {

            get { return _phoneNumber; }

            set { _phoneNumber = value; }

        }

 

        [DomainValidator("0522", "0523", "0524", "0525", "0528", // Cellcom

                        "0505", "0506", "0507", "0508", // Palaphone

                        "0544", "0545", "0546", "0547", // Orange

                        "0577", // Mirs

                MessageTemplate = "Not a valid cell phone area code")]

        public string ExctractCellPhoneAreaCode()

        {

            if (PhoneNumber.Length < 4)

                return null;

            else

                return PhoneNumber.Replace("-", "").Substring(0, 4);

        }

    }

נשים לב שאפשר לשים Validatorים של VAB על פונקציות!
יש מספר הגבלות ל-VAB עם פונקציות, בעיקר שהן לא מקבלות שום פרמטרים.

אגב, תחשבו איזה סמטוכה אם היינו צריכים להכניס את הלוגיקה הזאת של קידומת מספרים סלולריים לתוך ה-Regex...

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

נוסיף בתוך המחלקה שעבדנו עליה כל הזמן הזה, טיפוס מסוג פלאפון.

    public class VisitRating

    {

        ...

 

        private CellPhone _cellPhone;

 

        public CellPhone CellPhone

        {

            get { return _cellPhone; }

            set { _cellPhone = value; }

        }

    }

עכשיו יש לנו דילמה. הרי יש לנו שדות ב-Winforms ו-ASP.Net שצריכים וולידציה.
והם צריכים לפנות לוולידציה ל: (1) מחלקה מסויימת (2) מאפיין מסויים.
אי-אפשר להפנות אותם רק ל-CellPhone.PhoneNumber כי אז תתפספס הוולידציה על ()CellPhone.ExctractCellPhoneAreaCode ולהפך.

אז נפנה את הוולידציה שלנו, בשני ה-GUIים ל-VisitRating.CellPhone.


ב-Winforms יש לנו את השדה:

image_thumb30
ובהתאם נקבע שהוא יפנה ל-VisitRating.CellPhone

image_thumb31

ב-ASP.Net יש לנו את השדה הבא

image_thumb32

ונקבע לו מאפיינים שיצביעו גם על BL.VisitRating.CellPhone.
image_thumb33

 

עכשיו אתם שואלים, "אבל רגע, ג'סטין, VisitRating.CellPhone הוא בכלל מטיפוס CellPhone ואנחנו מבצעים וולידציה עד עכשיו רק על טיפוסים פרימיטביים."
נכון, בשביל שה-PropertyProxyValidator של ASP.Net וה-ValidationProvider של Winforms ידעו להציב את המחרוזת שקיבלנו ב-tbxCellPhone לתוך VisitingRating.CellPhone נצטרך להמיר ממחרוזת ל-CellPhone.
נדבר על זה עוד שנגיע לוולידציה.

ההמרה ממחרוזת ל-CellPhone היא יחסית פשוטה, פשוט נאתחל מופע חדש של BL.CellPhone עם המחרוזת הרלוונטית.

ב-ASP.Net נשתמש באירוע ה-ValueConvert:

        protected void Propertyproxyvalidator8_ValueConvert(object sender, ValueConvertEventArgs e)

        {

            e.ConvertedValue = new CellPhone(e.ValueToConvert.ToString());

        }

כאן קיבלנו את הערך של המחרוזת בתוך תיבת הטקסט כ-e.ValueToConvert והחזרנו אותו בתור e.ConvertedValue.

ב-Winforms גם נשתמש באירוע ה-ValueConvert, אבל בצורה קצת שונה:

        private void VisitRatingValidation_ValueConvert(object sender, ValueConvertEventArgs e)

        {

            if (e.TargetType == typeof(CellPhone))

            {

                e.ConvertedValue = new CellPhone(e.ValueToConvert.ToString());

            }

        }

הקוד שהשתמנו להמרה ממחרוזת לטיפוס פלאפון לא רלוונטיים כרגע (היות ונרחיב עליו הרבה בהמשך), אבל חשוב להבין למה היה צריך היה לבצע את ההמרה הזו.
אנחנו למעשה לוקחים TextBox.Text ורוצים להריץ עליו וולידציות של BL.CellPhone, ברור שנצטרך לבצע כאן המרה מסויימת.

 

ועכשיו נחזור לבעיה שראינו קודם, למה בכלל שהוולידציות שכתובת בתוך טיפוס BL.CellPhone ירוצו שאנחנו מבצעים וולידציה על BL.VisitRating.CellPhone?
נצטרך וולידטור שאומר שהוולידציה של הטיפוס VisitRating תלויה בוולידציה של הטיפוס CellPhone.

    public class VisitRating

    {

        ...

 

        private CellPhone _cellPhone;

 

        public CellPhone CellPhone

        {

            get { return _cellPhone; }

            set { _cellPhone = value; }

        }

    }

יהפוך ל

    public class VisitRating

    { 

        ...

  

        private CellPhone _phone;

        [ObjectValidator()]

        public CellPhone CellPhone

        {

            get { return _cellPhone; }

            set { _cellPhone = value; }

        }

    }

שימו לב שה-GUIים שלנו מריצים וולידציה על VisitRating.CellPhone ואין להם מושג בכלל מה הכללי וולידציה בתוך BL.CellPhone.

בואו נראה איך כל זה נראה ב-GUIים שלנו.
ננסה להכניס סתם טקסט:

image_thumb34

image_thumb36

ננסה להכניס את מספר הפלאפון שלי 054-6566789 שבתקווה גם יעבור וולידציה:

image_thumb37

ועכשיו נשנה את הקידומת של הפלאפון למשהו שלא קיים ב-DomainValidator שכתבנו. למשל לקידומת 0540 הלא קיימת.

image_thumb38

שמתם לב? מספר פלאפון וולידי, אבל הקידומת לא ברשימת הקידומות שלנו.

נראה איך היינו מוסיפים את כל הוולידטורים האלו דרך עורך הקונפיגיורציה הגרפי.
נתחיל מלהוסיף את הקונפיגיורציה של CellPhone.

image_thumb39

שימו לב שבהגדרה של CellPhone יש גם מתודה שעוברת וולידציה באמצעות ה-DomainValidator שלנו שבודק קידומות.

image_thumb41

נביט על ה-XML שקיבלנו מהעורך הגרפי בקובץ הקונפגיורציה BL.config:

<type defaultRuleset="default" assemblyName="BL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"

  name="BL.CellPhone">

  <ruleset name="default">

      <properties>

          <property name="PhoneNumber">

              <validator pattern="^05[0246]([-]{0,1})\d{7,8}$" options="None"

                patternResourceName="" patternResourceType="" messageTemplate="Must be a valid cell phone number"

                messageTemplateResourceName="" messageTemplateResourceType=""

                tag="" type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.RegexValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

                name="Regex Validator" />

          </property>

      </properties>

      <methods>

          <method name="ExctractCellPhoneAreaCode">

              <validator negated="false" messageTemplate="Not a valid cell phone area code"

                messageTemplateResourceName="" messageTemplateResourceType=""

                tag="" type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.DomainValidator`1[[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Microsoft.Practices.EnterpriseLibrary.Validation, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

                name="Domain Validator">

                  <domain>

                      <add name="0522" />

                      <add name="0523" />

                      <add name="0524" />

                      <add name="0524" />

                      <add name="0525" />

                      <add name="0528" />

                      <add name="0505" />

                      <add name="0506" />

                      <add name="0507" />

                      <add name="0508" />

                      <add name="0544" />

                      <add name="0545" />

                      <add name="0546" />

                      <add name="0547" />

                      <add name="0577" />

          </domain>

        </validator>

      </method>

    </methods>

  </ruleset>

</type>

הדבר היחידי הבאמת מעניין כאן זה שהקונפיגיורציה של CellPhone מכילה וולידציה על מתודה ומולנו אפשר לראות שיש תגית <methods> מיוחדת לזה ובתוכה מקוננות תגיות <method>, ובתוכן יש וולידציה כרגיל.

נוסיף את ההגדרה של ObjectValidator שיושב על VisitRating.

image_thumb42

אין שום דבר מעניין לפרט במאפיינים של ObjectValidator.
בקונפיוגרציה של VisitRating יש רק הנחייה כללית לבצע וולידציה על CellPhone לפי הכללים של הטיפוס CellPhone.

    <type defaultRuleset="default" assemblyName="BL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"

      name="BL.VisitRating">

      <ruleset name="default">

        <properties>

          ...

          <property name="CellPhone">

            <validator targetRuleset="" type="Microsoft.Practices.EnterpriseLibrary.Validation.Validators.ObjectValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

              name="Object Validator" />

          </property>

        </properties>

      </ruleset>

    </type>

 

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

יש לנו שלוש מחלקות:

1. FirstClass שבתוכה יש SecondClass

    public class FirstClass

    {

        private SecondClass _second = new SecondClass();

 

        public SecondClass Second

        {

            get { return _second; }

            set { _second = value; }

        }

    }

2. SecondClass שבתוכה יש ThirdClass

    public class SecondClass

    {

        private ThirdClass _third = new ThirdClass();

 

        public ThirdClass Third

        {

            get { return _third; }

            set { _third = value; }

        }

    }

3. ThirdClass עם מחרוזת שהיא לכל היותר שלושה אותיות.

    public class ThirdClass

    {

        private string _maxThreeLetterString;

 

        public string MaxThreeLetterString

        {

            get { return _maxThreeLetterString; }

            set { _maxThreeLetterString = value; }

        }

    }

נראה את זה בדיאגרמה:

image_thumb43

נכתוב קוד שקובע ערך ל-MaxThreeLetterString.

            FirstClass firstClassWithValidString = new FirstClass();

            firstClassWithValidString.Second.Third.MaxThreeLetterString = "abc";

כאן, הלכנו דרך FirstClass.SecondClass.ThirdClass והגענו ל-MaxThreeLetterString.

עכשיו נראה למה צריך וולידציה.

            FirstClass firstClassWithNonValidString = new FirstClass();

            firstClassWithNonValidString.Second.Third.MaxThreeLetterString = "hello world";

נצטרך איכשהו לאכוף את זה ש-MaxThreeLetterString הוא אכן לכל היותר 3 אותיות.
אנחנו כבר מתורגלים ב-VAB אז נוסיף StringLengthValidator ל-MaxThreeLetterString.

    public class FirstClass

    {

        private SecondClass _second = new SecondClass();

 

        public SecondClass Second

        {

            get { return _second; }

            set { _second = value; }

        }

    }

 

    public class SecondClass

    {

        private ThirdClass _third = new ThirdClass();

 

        public ThirdClass Third

        {

            get { return _third; }

            set { _third = value; }

        }

    }

 

    public class ThirdClass

    {

        private string _maxThreeLetterString;

        [StringLengthValidator(3)]

        public string MaxThreeLetterString

        {

            get { return _maxThreeLetterString; }

            set { _maxThreeLetterString = value; }

        }

    }

נריץ את הקוד הבא שמאתחל את המחלקה FirstClass הלא וולידית, שולח אותה לוולידציה ומדפיס אם היא וולידית או לא.

            FirstClass firstClassWithNonValidString = new FirstClass();

            firstClassWithNonValidString.Second.Third.MaxThreeLetterString = "hello world";

 

            ValidationResults results = Validation.Validate(firstClassWithNonValidString);

            Console.WriteLine(results.IsValid);

והתוצאה - ברור שהיא בסדר גמור.

image_thumb46

אז מה עם הוספנו StringLengthValidator על MaxThreeLetterString?
לכל וולידציה שתתבצע על FirstClass אין שום דרך או סיבה להכיר את הוולידציות של ThirdClass.

נצטרך להגיד לוולידציות של FirstClass לכלול את הוולידציות של SecondClass שבתוכה, ולוולידציות של SecondClass לכלול את הוולידציות של ThirdClass שבתוכה.

    public class FirstClass

    {

        private SecondClass _second = new SecondClass();

        [ObjectValidator()]

        public SecondClass Second

        {

            get { return _second; }

            set { _second = value; }

        }

    }

 

    public class SecondClass

    {

        private ThirdClass _third = new ThirdClass();

        [ObjectValidator()]

        public ThirdClass Third

        {

            get { return _third; }

            set { _third = value; }

        }

    }

 

    public class ThirdClass

    {

        private string _maxThreeLetterString;

        [StringLengthValidator(3)]

        public string MaxThreeLetterString

        {

            get { return _maxThreeLetterString; }

            set { _maxThreeLetterString = value; }

        }

    }

ורק עכשיו כאשר נריץ את הקוד הבא, נראה שהוא אכן לא וולידי:

            FirstClass firstClassWithNonValidString = new FirstClass();

            firstClassWithNonValidString.Second.Third.MaxThreeLetterString = "hello world";

 

            ValidationResults results = Validation.Validate(firstClassWithNonValidString);

            Console.WriteLine(results.IsValid);

image_thumb51

למעשה, הוולידציה של המחלקות גולשת ממחלקה למחלקה ומכילה בתוכה את הלוגיקה של אותה מחלקה.

image_thumb43

החצים הם לא רק קשרי אסוצאיציה, אלא הם גם הכיוון אליו תגלוש הוולידציה שלנו.

 

Comments

No Comments