DCSIMG
Question from Tapuz .Net forum: Dynamically loaded UserControls disappear on PostBack - Justin myJustin = new Justin( Expriences.Current );

Question from Tapuz .Net forum: Dynamically loaded UserControls disappear on PostBack

שאלה:

פקדי מסוג UserControls שהוספתי לדף נעלמים ב-PostBack, למה זה ומה ניתן לעשות כדי למנוע את זה?

 

תשובה:

(נכתבה בדוא"ל חצי שנה לפני שאלת השאלה, ציטוט מדוייק מתוך אוטלוק)

בהמשך למה שדיברנו אתמול על טעינה דינמית של User Controls  לדף.

אם לחזור על מה שאמרנו ועל הבעיה הספציפית:
1. יש לך בדף כפתור.
2. בלחיצה על הכפתור נטען UC1 דינמית.
3. בתוך UC1 יש כפתור.
4. בלחיצה על הכפתור בריצה בחזרה לשרת, אין שום דרך לזכור שבשלב "2" נוצר UC1 דינמי.
5. ולכן כתוצאה של "4" נקבל שאחרי ה-PostBack אחרי הלחיצה אין UC1 על הדף.

הבעיה הכללית יותר שלך הייתה ליצור UC בתוך UC.

תן לי להציע פתרון כללי – לבנות תשתית קטנה שתתלבש על הירושה מ-Page או UserControl שהתפקיד שלה יהיה לדאוג לבנות מחדש פקדים דינמיים.
כלומר, שלא תצטרך ידנית ברמת הדף להוסיף כל פעם מחדש את הפקדים שהוספת לפני PostBack.

נתחיל מדף ASP.Net פשוט:

image

כפתור, שלוחצים עליו – רץ קוד בשרת.

image

הקוד מוסיף UC שנטען דינמית.
ככה הדף נראה לפני לחיצה:

clip_image006[4]

וככה אחרי:

clip_image008[4]

שורה ראשונה: הפקד מדפיס את ה-ClientID.
שורה שנייה: סתם טקסט ב-Label וכפתור שנגיע עליו עוד שנייה.
שורה שלישית: TextBox וסתם כפתור שגורם PostBack (אין אירוע בצד השני)

אין שום דבר מיוחד ב-ASP.Net declarative של ה-UC:

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="WebUserControl.ascx.cs" EnableViewState="true" Inherits="WebUserControl" %>
<div style="border: solid 5px black; padding: 5px; margin: 5px;">
<asp:Label EnableViewState="true"  ID="lblClientID" runat="server" Text="Label">Client ID: </asp:Label>  <br />
<asp:Label EnableViewState="true"  ID="Label1" runat="server" Text="Label">Hello world</asp:Label>
<asp:Button runat="server" ID="btnAddNewSonControlInPostack" OnClick="myBtn1_Click" Text="Add New Son Control In Postack"/><br />
<asp:TextBox runat="server" ID="tbx1" />
<asp:Button runat="server" ID="btnSomeButton" Text="Write something and textbox, press me and see what happends..." />
<br /></div>

נכניס קצת טקסט לתוך ה-TextBox ונלחץ על הכפתור לידו:

clip_image010[4]

ובטעינה חזרה השם שלי עדיין מופיע שם.

clip_image012[4]

שימו לב, לחצנו על כפתור ש"סתם" גורם ל-Postback (אין שום מתודה שנרשמה ללחיצה על הכפתור), היה PostBack מלא, לא כתבנו קוד ספציפי לטעון מחדש את ה-UC והוא נשאר על הדף עם ערכים של ViewState ו-PostBack.

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

נראה את הקוד שירוץ אם נלחץ Add New Son Controls In PostBack (הכפתור בשורה השנייה)

clip_image014[4]

יצירה של UC מקונן חדש (בתוך UC קיים), נתנו לו ID שאנחנו יודעים שלא חוזר על עצמו והוספנו לילדים של הפקד.
נלחץ על Add New Son Control In Postback (הכפתור בשורה השנייה) ונקבל:

clip_image016[4]

אפשר לראות (לפי ה-ClientID) שמדובר בפקד שמקונן לפקד הנוכחי.
נוכל להמשיך ללחוץ על הכפתור הזה (בפקד הזה ובפקדים הילדים החדשים שנוצרים), ובסוף נקבל:

clip_image018[4]

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

אז מה הקסם מאחורי זה שיש לנו עדיין את אותם פקדים אחרי PostBack מלא?
קומפלט עם מידע שבא מה-ViewState ולטעון לתוכם מידע מה-PostBack?

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

image

ובצד השני, בטעינה של מידע Viewstate נראה: image

לא חשוב הקוד הספציפי, אלא חשוב שכאן ב-LoadViewState אנחנו קוראים את הערך שהכנסנו ב-SaveViewState.

מה קורה כאן מאחורי הקלעים?
נכניס "איזהשהו" ערך שמייצג את היררכיית ה-UC על הדף לתוך ה-ViewState, היה PostBAck, נקרא את אותו ערך ולפיו נבננה מחדש את היררכיית ה-UC בדף.

נראה מחלקה קטנה ופשוטה:

image

בעזרת המחלקה הזו נתעד UC – את הנתיב שבעזרתו טענו את קובץ ה-ASCX ואת ה-ID שלו על הדף.
בנוסף, נרשום בתוכו אם יש UCים נוספים מקוננים.

אז איך עובד ה-Save?

    private UserControlHierachy GetUserControlHierachy(UserControl controlToRebuildAndCheckIfHasNestedUserControls)

    {

        UserControlHierachy ReturnValue = new UserControlHierachy();

        ReturnValue.Path = controlToRebuildAndCheckIfHasNestedUserControls.AppRelativeVirtualPath;

        ReturnValue.ID = controlToRebuildAndCheckIfHasNestedUserControls.ID;

 

        foreach (Control controlToCheckIfUserControl in controlToRebuildAndCheckIfHasNestedUserControls.Controls)

        {

            if (controlToCheckIfUserControl is UserControl)

            {

                ReturnValue.NestUserControls.Add(GetUserControlHierachy(controlToCheckIfUserControl as UserControl));

            }

        }

 

        return ReturnValue;

    }

נקבל UC שנרצה להוסיף להיררכיה הסופית.
ניצור מחלקת מידע חדשה שתחזיק את ה-ID שלו ואת ה-Path שלו (ונשתמש בהם אחר-כך כדי ליצור אותו מחדש).

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

איך נראה ה-Load?
בדיוק הפוך.

    private void RebuildUserControlHierachy(UserControlHierachy hierachyToRebuildItsControls, Control parent)

    {

        UserControl newUserControl = (UserControl)LoadControl(hierachyToRebuildItsControls.Path);

        newUserControl.ID = hierachyToRebuildItsControls.ID;

        parent.Controls.Add(newUserControl);

 

        foreach (UserControlHierachy hierachyToRebuildItsControlsInCurrentHierachy in hierachyToRebuildItsControls.NestUserControls)

        {

            RebuildUserControlHierachy(hierachyToRebuildItsControlsInCurrentHierachy, newUserControl);

        }

    }

אנחנו מקבלים מחלקת מידע חדשה והורה לקונן לתוכה את ה-UC שניצור.
נטען אותו לפי נתיב ASCX ו-ID ונוסיף להורה.

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

 

כמה הערות חשובות:

1. זה סה"כ POC להראות איך ניתן להשתמש ב-ViewState כדי לשמור על היררכיית פקדים אוטומטית.
מקרים שלא טיפלנו כאן: טעינה ושמירה פקדים דינמיים שהם לא UC, איך מבדילים בין פקד דינמי לפקד רגיל שנוצר ב-ASP.Net Declarative, מה קורה אם באמצע ההיררכיה יש סתם Container Control כמו Panel או PlaceHolder.

2. יש כאן בעיית אבטחה שצריך לשים עליה את הדעת שלפי ה-ViewState נטען ASCX, עדיף כאן לעבוד עם משהו מבוסס Tokenים (1 מייצג moshe.ascx, 2 מייצג shlomo.ascx, ...)

3. זה לא נבדק מול UpdatePanel, אבל היות וכל מחזור הדף רץ שם לא אמורה להיות בעיה.

4. ייתכן וכי יש צורך להריץ פעמים את Base.LoadViewState, פעם אחת כדי לקבל ViewState של המחלקת מידע ופעם אחת לטעון לתוך המחלקות החדשות את ה-ViewState אחרי שנוצרו.

5. עדיף כמובן (כמו שציינתי) להוסיף את הקוד הספציפי לפתרון התשתיתי הזה לתוך BasePage או BaseUserControl.

 

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

קוד של שתי הדפים האלו:  http://www.JustinAngel.Net/files/DynamicUserControls.zip

 

 

קישור: http://www.tapuz.co.il/tapuzforum/main/Viewmsg.asp?forum=831&msgid=109336002

Published Thursday, November 29, 2007 2:58 PM by Justin-Josef Angel [MVP]

Comments

No Comments