Silverlight Validation & Data Annotation
בשבוע האחרון נתקלתי במספר שאלות על נושא Validation ולכן החלטתי לעשות סדר ולהסביר את המנגנון שנמצא ב-SL .
אחד התפקיד העיקרים של ה- Validation בשכבת ה-UI הוא לבדוק נכונות של הנתונים שמוזנים ע"י המשתמש.
היום הדרך השכיחה ביותר לחיבור בין הפקדים לאוביקטים שמחזקים את המידע הוא שימוש ב- DataBinding. מכאן נובע שמיקרוסופט כדי להקל אלינו הכניסה בתוך האוביקט Binding יכולות של Validation.
אוביקט ה-Binding לקח את הערך מפקד ה-UI למשל TextBox, את הערך מהפרופרטי Text ומכניס אותו לאוביקט שלנו. עכשיו נשאל את השאלה מי עושה לערך המוכנס את ה- Validationואיך מדווחים על שגיאות?
יש מספר דרכים לעשות את הבדיקות לערך המוכנס:
1. בדיקה רגילה בתוך ה-Set של ה-Property ( Databinding עובד רק על פרופרטי Public)
2. עבודה עם Attributes ששיכים למשפחת Data Annotation
כיום העבודה עם Data Annotation היא הדרך הפופולרית יותר, בגלל הפשטות והיכולת לעבוד עם סיפרית ה-Attribute גם בצד השרת וגם בצד הלקוח. WCF RIA Services ו- ASP.NET MVC עובדים עם טכנולוגיה זו.
כאשר הבדיקה נכשלת איך מדווחים לשכבת ה-UI שהערך המוקלד לא נכון? להלן מספר דרכים לפתרון:
1. זריקת טעות
ע"פ דעתי זו הדרך הגרועה ביותר כי זריקת Exception בתור ה-Set של הפרופרטי מעקב את ה-UI ויש בתוך ה- Exceptionיותר מידע ממה שצריך. כדי שה-Binding יתיחס לשגיאות צריך להדליק אופציה זו.
<TextBox Text="{Binding Model, ValidatesOnExceptions=True} />
2. עבודה עם ממשק IDataErrorInfo ( ישן ולא נוח )
באחריותך לדאוג לעדכן את Error פרופרטי, אם יש מספר טעויות באחריותכם לקבוע סימון מפריד בין הודעה להודעה כי Error מחזיר string. גם ה-Indexer מעצבן כי אני צריך אותם לדברים אחרים.
כדי שה-Binding יתיחס לשגיאות צריך להדליק אופציה זו.
<TextBox Text="{Binding Model, ValidatesOnDataErrors=True}" />
3. עבודה עם ממשק INotifyDataErrorInfo
הרבה יותר נוח מהקודם בגלל האירוע שמדווח לי שיש בעיה ואז אני יכול למשוך את כל הבעיות דרך המתודה GetErrors ע"פ הפרופרטי הרלוונטי. כדי שה-Binding יתיחס לשגיאות צריך להדליק אופציה זו. (ValidatesOnNotifyDataErrors=True, זה ערך בברירת המחדל )
<TextBox Text="{Binding Model, ValidatesOnNotifyDataErrors=True}" />
ב-WPF לא קיים INotifyDataErrorInfo, אני מקווה מאוד שבגירסה 5 הם יוסיפו אותו.
4. עבודה עם אוביקט Validator
ב-SL רק הפקד DataGrid יודע לעבוד עם Data Annotation כל שאר הפקדים מצריכים מאיתנו לכתוב קוד שיודע להבין את Attribute ולפעול בהתאם. לשם כך נשתמש במחלקה Validator.
כדי שה-Binding יתיחס לשגיאות צריך להדליק את האופציות הבאות:
<TextBox Text="{ Binding Model,
ValidatesOnExceptions = True,
NotifyOnValidationError= True }" />
מה עושה, NotifyOnValidationError= True? מפעיל Routed Event כדי לדווח לאבא.
הבעיה כאן שהמחלקה Validator זורקת טעות, וזה לא לטעמי, מהסיבות שפרטתי בסעיף 1. כדי שלא יהיה לנו Exceptions אנחנו צריכים לעבוד עם המתודה TryValidateProperty. הבעיה שאז איך ה- Binding ידע על השגיאות? התשובה לעבוד עם אחד הממשקים, וב-SL כמובן שנבחר ב- INotifyDataErrorInfo. אם מסתכלים על המתודה TryValidateProperty אנחנו רואים שהיא מקבלת <ICollection<ValidationResult ששם מאוחסנים כל הטעויות של Validation. לכן הממשק INotifyDataErrorInfo השתמש במאגר זה לדווח על טעויות. לדוגמא לכך אפשר לקרוא בפוסט Silverlight 4 MVVM Validation using INotifyDataErrorInfo.
עד כאן הסברתי איך מתבצעת ה-Validation ואיך אוביקט ה-Binding מזהה שיש בעיה. מה שנשאר לנו להבין, איך הפקדים מסמנים שיש בעיה? איך אפשר לשנות עיצוב זה?
כמעט לכל הפקדים שיכולים לקבל Input יש מצב של טעות (InvalidFoucused & InvalidUnfocused) שבעזרת Blend אפשר לשנות עיצוב זה בקלות.
השאלה שנשאלת היא מי מעביר את ה-State למצב של InvalidFocused? האם זה ה-Binding או הפקד עצמו? התשובה היא שה- Bindingזורק אירוע שנתפס ע"י הפקד, והפקד מעביר את עצמו למצב של InvalidFocused או InvalidUnfocused. אם אנחנו רוצים גם להירשם לאירוע של השגיאות אנחנו נרשמים לאירוע ה- BindingValidationError שמוגדר ב- FrameworkElement.
סיכום:
אני מאוד אוהב את העבודה עם Data Annotation והתוספת שמחברת את השגיאות ל- INotifyDataErrorInfo. WCF RIA Services נותן לנו את זה בחינם, אך אין בעיה גם לכתוב את השורות אלו בעצמנו ב- ViewModelBase. פתרון עוד יותר נקי זה להכניס את זה לתוך הפקד כמו שעשו ב-DataGrid ו-DataForm.