DCSIMG
Question from Tapuz .Net forum: Returning an Enum value and name from Microsoft AJAX WebServices (Creating Custom JavaScriptConverter, Javascript Enums) - Justin myJustin = new Justin( Expriences.Current );

Question from Tapuz .Net forum: Returning an Enum value and name from Microsoft AJAX WebServices (Creating Custom JavaScriptConverter, Javascript Enums)

שאלה:

 אני עובדת עם Microsoft AJAX ומחזירה מהשרת ללקוח Enumים. הבעיה היא שבמקום להעביר ללקוח את הטקסט של ה-Enum אנחנו מקבלים את האינדקס של הערך הנבחר.

אפשר לשנות את ההתנהגות הזו שיעביר את הטקסט של הערך? אולי בצמוד לאינדקס?

 

תשובה:

 בואו נראה דוגמה לבעיה לפני שנפתור אותה.

נתקין Microsoft AJAX ו-ASP.Net Futures אחרי שהורדנו אותם מכאן - http://ajax.asp.net/downloads/default.aspx?tabid=47.

 ניצור אתר Microsoft AJAX CTP Enabled חדש.

נכתוב Enum חדש בדוט נט.

    public enum myEnum

    {

        First,

        Second

    }

ניצור דף בסיסי שמשתמש ב-PageMethods כדי להחזיר ערכים ללקוח.

 <body onload="UseEnum()">

    <form id="form1" runat="server">

        <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true" />

        <script type="text/javascript">

        function UseEnum()

        {

            PageMethods.ReturnMyEnum(onReturnMyEnum);

        }

 

        function onReturnMyEnum(result)

        {

            alert(result);

        }           

        </script>

    </form>

יש לנו ScriptManager בדף עם EnablePageMethods=True כדי שנוכל לקרוא למתודות public static שיושבות ב-code behind של הדף מתוך ג'אווה סקריפט.
שהטופס עולה אנחנו קוראים לפונקציה בג'אווה סקריפט בשם UseEnum שמריצה מתודה (גם באנגלית: Method) צד-שרת בשם ReturnMyEnum והערך שהמתודה מחזירה מודפס באמצעות alert.

נראה את ReturnMyEnum.

public partial class Default3 : System.Web.UI.Page

{

    [WebMethod()]

    public static myEnum ReturnMyEnum()

    {

        return myEnum.First;

    }

}

נריץ את הדף ומה שנקבל זה:

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

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

    [WebMethod()]

    public static string ReturnMyEnum()

    {

        return myEnum.First.ToString();

    }

ושנריץ את הדף נקבל:

יש עם הפתרון הזה כמה בעיות:

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

2. המילה "First" לא מייצגת שום דבר, היא רק מחרוזת. היא לא חלק מאיזה Enum גדול יותר.

 

פתרון נוסף, יפתור את הבעיה המקורית ובעיה מספר 2 שהעלנו.
נמשיך להחזיר ללקוח את האינדקס הנבחר של ה-Enum, אבל נשתמש ב-Microsoft AJAX Javascript Enhancments כדי להגדיר את ה-Enum בצד-לקוח.

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

אם זו ההגדרה של myEnum ב-#C

    public enum myEnum

    {

        First,

        Second

    }

אז זו תהיה ההגדרה של myEnum בג'אווה סקריפט

        myEnum = function() {}

        myEnum.prototype =

        {

            First : 0,

            Second : 1

        }

        myEnum.registerEnum("myEnum");   

נחזור ונדגיש שחזרנו לעבוד עם החזרה רגילה של Enumים (שלמעשה מחזירים את האינדקס הנבחר ב-Enum).

    [WebMethod()]

    public static myEnum ReturnMyEnum()

    {

        return myEnum.First;

    }

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

        function onReturnMyEnum(result)

        {

            alert( result + " " + myEnum.toString(result) );

        }

שנריץ נקבל:

 

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

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

אם נרצה נוכל לנפתור את הבעיה הזו באמצעות #Script, פריימוורק צד-שלישי שמקמפל #C לג'אווה סקריפט. אפשר להוסיף ל-Build משימה שמקמפלת את ה-Enum צד-שרת שלנו ל-Enum צד-לקוח.

 

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

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

בואו נביט על משהו מעניין בקובץ ה-web.config שלנו (שיופיע רק ב-CTP Enabled Microsoft AJAX Website).

    <system.web.extensions>

        <scripting>

            <webServices>

                <jsonSerialization maxJsonLength="500">

                    <converters>

                        <add name="DataSetConverter" type="Microsoft.Web.Preview.Script.Serialization.Converters.DataSetConverter, Microsoft.Web.Preview, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

                        <add name="DataRowConverter" type="Microsoft.Web.Preview.Script.Serialization.Converters.DataRowConverter, Microsoft.Web.Preview, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

                        <add name="DataTableConverter" type="Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter, Microsoft.Web.Preview, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

                    </converters>

                </jsonSerialization>

            </webServices>

        </scripting>

    </system.web.extensions>

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

מה השלוש שורות האלו של <converters> ב-web.config אומרות לנו?
שכדי להחזיר מצד-שרת DataSetים ו-DataTables אנחנו רושמים משהו שנקרא Converter והוא דואג להמיר אותם לפורמט שניתן לעבוד איתו בלקוח.

אז בואו גם אנחנו ניצור Converter משלנו. לפני זה נחזור ונזכיר שאנחנו מחזירים כרגיל מהצד-שרת Enum:

    [WebMethod()]

    public static myEnum ReturnMyEnum()

    {

        return myEnum.First;

    }

ניצור מחלקה משלנו שהתפקיד שלה יהיה לקחת Enum כלשהו (לא בהכרח myEnum) ולהמיר אותו לפורמט שנקבל בצד-לקוח Value ו-Index.

נתחיל מלרשת את המחלקה האבסטרקטית JavaScriptConverter.

    public class EnumConverter : System.Web.Script.Serialization.JavaScriptConverter

    {

        public override IEnumerable<Type> SupportedTypes

        {

            get { throw new Exception("The method or operation is not implemented."); }

        }

 

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)

        {

            throw new Exception("The method or operation is not implemented.");

        }

 

        public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)

        {

            throw new Exception("The method or operation is not implemented.");

        }

    }

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

1.  SupportedTypes שמחזיר רשימה של טיפוסים בהם ה-Converter הספציפי הזה מטפל

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

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

 

במקרה שלנו אמרנו ש-EnumConverter יתמוך בכל Enum, אז נדאג להצהיר על כך ב-SupportedTypes.

        public override IEnumerable<Type> SupportedTypes

        {

            get

            {

                return new ReadOnlyCollection<Type>(new Type[] { typeof(Enum) });

            }

        }

נמשיך בלממש את Serialize.

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)

        {

            Enum curEnum = obj as Enum;

            if (curEnum == null)

                throw new ArgumentException("only Enum must be sent to EnumConverter");

 

        }

אמרנו הרי שאנחנו תמיד נטפל ב-Enumים, אז נמיר את האובייקט שקיבלנו ל-Enum ולמקרה שקיבלנו משהו שהוא לא Enum נזרוק חריגה.

עכשיו מגיע החלק הבאמת מעניין.

        public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)

        {

            Enum curEnum = obj as Enum;

            if (curEnum == null)

                throw new ArgumentException("only Enum must be sent to EnumConverter");

 

            IDictionary<string, object> ReturnValues = new Dictionary<string, object>();

            ReturnValues.Add("name", obj.ToString());

            ReturnValues.Add("value", Convert.ToInt32(obj));

 

            return ReturnValues;

        }

אמרנו ככה - כל Enum שנקבל, נחזיר את הטקסט שלו במאפיין (גם באנגלית: Property) בשם name ואת האינדקס שלו בתוך value.
עוד שנייה גם נראה איך ניגש למאפיינים האלו בצד-לקוח.

את Deserialize לא נממש כי לפחות בדוגמה כרגע לא נשלח בחזרה לשרת ערכים שה-Converter הזה אמור לטפל בהם.

נרשום את ה-Converter ב-web.config שלנו. (בהנחה והוא יושב ב-App_code)

                <jsonSerialization maxJsonLength="500">

                    <converters>

                        <add name="EnumConverter" type="myConverters.EnumConverter"/>

                        <add name="DataSetConverter" type="Microsoft.Web.Preview.Script.Serialization.Converters.DataSetConverter, Microsoft.Web.Preview, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

                        <add name="DataRowConverter" type="Microsoft.Web.Preview.Script.Serialization.Converters.DataRowConverter, Microsoft.Web.Preview, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

                        <add name="DataTableConverter" type="Microsoft.Web.Preview.Script.Serialization.Converters.DataTableConverter, Microsoft.Web.Preview, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

                    </converters>

                </jsonSerialization>

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

        <script type="text/javascript">

        function UseEnum()

        {

            PageMethods.ReturnMyEnum(onReturnMyEnum);

        }

 

        function onReturnMyEnum(result)

        {

          alert( result.value + " " + result.name);

        }         

        </script>

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

 

הקוד שעבדנו עליו זמין להורדה כאן - http://www.JustinAngel.Net/files/Example-AjaxEnum.zip

Published Wednesday, June 06, 2007 1:49 PM by Justin-Josef Angel [MVP]

Comments

No Comments