DCSIMG
October 2010 - Posts - שלמה גולדברג (הרב דוטנט)

שלמה גולדברג (הרב דוטנט)

מרצה בסלע ויועץ בעולם ה - net.

October 2010 - Posts

The Complete Guide Of ASP.NET AJAX

בס"ד

המדריך המלא ל – ASP.NET AJAX

 

הורדת המדריך בגרסת PDF.   
הורדת קבצי המקור של דוגמאות הקוד שבמדריך.

תוכן העניינים:

·        דרישות קדם.

·        מטרת המדריך.

·        מה זה בכלל AJAX.

·        Native AJAX – איך עבדו פעם ואיך הכול מתבצע מאחורי הקלעים.

o       עבודה עם XmlHttpRequest.

o       קריאה ל – HttpHandler.

§        GET.

§        .POST

o       הפעלת מתודות של WebService.

§        GET.

§        POST.

o       סיכום ביניים.

o       תרגיל – קבלת רשימת ערים לפי מדינות.

o       הכרת פורמט JSON.

o       קבלת אובייקטים ושליחת פרמטרים ב – JSON.

§        שימוש ב – JavaScriptSerializer.

§        שימוש במנגנון ScriptService כדי להחזיר JSON.

o       סיכום ביניים.

o       תרגיל – כתיבת AutoCompleteTextBox.

·        עבודה עם ICallbackEventHandler.

o       כיצד עובדים עם המנגנון.

o       תרגיל – מימוש מחשבון בעזרת ICallbackEventHandler.

·        היכרות עם ScriptManager.

o       תחילת העבודה.

o       הפעלה של WebService בעזרת ScriptManager.

o       הפעלה של מתודות בדף (PageMethods) בעזרת ScriptManager.

·        עבודה עם UpdatePanel.

·        היכרות עם Ajax Control Toolkit

 

 

דרישות קדם.

כדי להבין את המדריך הזה וכדי להשיג את המטרות שהמדריך נכתב עבורן יש להכיר:

·        Html

·        Xml

·        Java Script

·        ASP.NET

·        Web Service

 

מטרת המדריך:

הסיבה לכתיבת מדריך זה היא לרכז במקום אחד את כל (כמעט הכול) מה שקשור לעבודה ב – AJAX בעולם ה – ASP.NET, יש הרבה חומר באינטרנט (גם אצלי בבלוג) אבל רציתי לכתוב מדריך שמי שיקרא אותו ידע בסופו לכתוב AJAX ולהבין AJAX, חשוב לי לא רק יכולת הכתיבה ב – AJAX אלא  גם ההבנה מה קורה מאחורי הקלעים כמשתמשים במנגנונים המובנים של המערכת.

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

 

מה זה בכלל AJAX.

לפני שנבין מה זה AJAX ולמה המציאו אותו, נוודא שאנחנו זוכרים את התהליך שמתרחש בכל פעם שבו אנו לוחצים על לחצן בדף ASP.NET.

כשאנו גולשים לאתר אינטרנט כלשהו הדפדפן שולח בקשה (Request) לשרת ומבקש את הדף שאליו גלשנו (הפנייה יכולה להיות ב – GET או ב – POST – לא ניכנס כאן להבדלים, לקריאה נוספת על הנושא), במידה והשרת מצא את הדף שביקשנו הוא שולח תשובה (Response) הדפדפן מקבל את התשובה שבדרך כלל מכילה html ומציג את התשובה בדפדפן.

כשהמשתמש לוחץ על אחד מהלחצנים בעמוד מתרחש תהליך שנקרא Post Back, אוספים את כל המידע מהלקוח (כל האלמנטים בעמוד שהם מסוג input) ואת כל העוגיות ועוד כל מיני דברים מעניינים ושולחים Request חדש לשרת עם כל המידע שנאסף, השרת מקבל את המידע מנתח אותו ושולח תשובה (שהיא תהיה html חדש).

וכמובן בנוסף לכל זה כשמדובר בדף של ASP.NET יש את תהליך Page Life Cycle. שהוא מורכב וגורם להרבה עבודה בשרת.

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

כדי לפתור את הבעיה הזאת החליט מי שהחליט להמציא את המושג Asynchronous JavaScript and XML – מה שמוכר בראשי התיבות שלו AJAX, המשמעות של הראשי תיבות היא שליחת Request מ – Java Script בצורה אסינכרונית (כלומר מבלי לתקוע את הדפדפן עד שיש תשובה) ולקבל את ה – Response בפורמט XML.

למשל – במידה ונרצה לחשב שני מספרים - במקום לשלוח את כל תוכן הדף לשרת נפעיל מתודה בשרת בעזרת יצירת Request מ – Java Script, השרת יחזיר את רק אץ סכום המספרים ולא את כל הדף מחדש וב – Java Script נציג היכן שצריך להציג את התוצאה.

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

 

Native AJAX – איך עבדו פעם ואיך הכול מתבצע מאחורי הקלעים

כמו כל דבר בעולם התכנות, יש המון עטיפות ושכבות לכל קומפוננט, וגם ל – AJAX יש, אנחנו נכיר מאוחר יותר את הספרייה של ASP.NET, אבל חשוב להבין מה בעצם קורה מאחורי הקלעים ומה אותם עטיפות עושים בפועל, ולכן אנחנו נתחיל עם הבסיס ונכתוב קוד AJAX בסיסי ביותר, ולאחר מכן נראה כיצד העטיפות עוזרות לנו לכתוב קוד קל יותר.

עבודה עם XmlHttpRequest

כדי לעבוד ב – AJAX קיים ב – Java Script אובייקט מיוחד בשם XmlHttpRequest, בעזרתו ניתן לשלוח Request לשרת מ – Java Script.

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

open(method, url, async)               

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

·        method – יכול לקבל GET או POST בהתאם לצורת שליחת הבקשה העדיפה עלינו.

·        url – הכתובת של הדף או המתודה שאנחנו רוצים לקבל.

·        async – במידה ונשלח true הבקשה תשלח בצורה אסינכרונית כלומר הדפדפן לא יתקע והגולש יכול להמשיך לשוטט להנאתו בדף, לעומת זאת אם נשלח false, בזמן שליחת הבקשה המשתמש לא יכול לעשות כלום בעמוד ומחכים לתשובה שהשרת יחזיר.

 

send(data)

כאשר קוראים לו מתבצעת שליחת ה – Request בפועל, המתודה יכולה לקבל data ונשתמש בזה כשנגדיר את method כ – POST, מכיוון שב -  GET אין משמעות לשלוח data מכיוון שאת כל המידע אנחנו אמורים לשלוח ב – Query String.

 

readyState

משתנה המכיל את המידע אודות המצב הנוכחי של הבקשה, הערך יכול להיות בין 0-4

·        0 – האובייקט נוצר אך עדיין לא אותחל, זה יהיה המצב לפני הקריאה למתודת open

·        1 – המתודה open נקראה אבל המתודה send עדיין לא נקראה.

·        2 – המתודה send נקראה אבל עדיין לא הגיע שום תשובה מהשרת.

·        3 – חלק מהמידע חזר אבל עדיין לא כולו.

·        4 – כל המידע חזר ואפשר כבר להשתמש בו.

 

status

מכיל את המידע על מצב ה – Response, כלומר האם הבקשה עברה כמו שצריך והתשובה שחזרה מכילה את מה שציפינו שתכיל, הערך יהיה 200 עבור בקשה תקנית, 404 עבור "דף לא נמצא"  (לרשימת הקודים המלאה).

 

onreadystatechange

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

xhr.onreadystatechange = function() {

    if (xhr.readyState == 4 && xhr.status == 200) {

        //.. some code

    }

};

 

 

נרשמנו לאירוע של שינוי המצב ונרצה לבצע קוד כלשהו רק בתנאי שהכול תקין (כלומר המצב עומד כרגע על הערך 4 – קבלנו תשובה מהשרת – והסטאטוס הוא 200 – הכול תקין) נרצה למשל להציג את התוצאה.

 

responseText

מכיל את התשובה שקבלנו מהשרת בצורה טקסטואלית.

 

responseXML

מכיל את התשובה שקבלנו מהשרת כאובייקט XML ויש לנו מתודות על האובייקט כדי לחלץ את המידע.

 

setRequestHeader

משמש אותנו לשלוח מידע נוסף לשרת – נראה את השימוש בו יותר מאוחר.

 

קריאה ל - Http Handler (הדוגמא המלאה נמצאת ב - NativeAJAX\GetHtmlOfOtherPage\Handler)

          GET

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

נגדיר Handler חדש שמקבל ב – Query String את שני המספרים וכותב את התוצאה לתוך ה – Response

public void ProcessRequest(HttpContext context)

{

    context.Response.ContentType = "text/plain";

 

    int num1 = int.Parse(context.Request["n1"]);

    int num2 = int.Parse(context.Request["n2"]);

 

    context.Response.Write((num1 + num2).ToString());

}

 

 

 כעת נכתוב את צד הלקוח שיפנה לאותו handler ישלח את הערכים ויציג את התשובה ב – alert

<body>

    <input type="text" id="txt1" />

    <input type="text" id="txt2" />

    <input type="button" value="Use Ajax" onclick="Run()" />

</body>

 

function Run() {

    var num1 = document.getElementById('txt1').value;

    var num2 = document.getElementById('txt2').value;

 

    var xhr = new XMLHttpRequest();

 

    var qs = '?n1=' + num1 + '&n2=' + num2;

    var url = "Add2Numbers.ashx" + qs;

    xhr.open("GET", url, true);

 

    xhr.onreadystatechange = function(response) {

        if (xhr.readyState == 4 && xhr.status == 200) {

            alert(xhr.responseText);

        }

    };

 

    xhr.send();

}

 

בשלב הראשון מוציאים את הערכים מתוך תיבות הטקסט, לאחר מכן מייצרים מופע של XmlHttpRequest, לאחר מכן מייצרים את ה – url שמורכב מהכתובת של ה – handler ושליחת הפרמטרים ב – query string, מפעילים את מתודת open ומגדירים שה – request ישלח ב – GET, לאחר מכן נרשמים ל – onreadystatechange ובזמן שהתשובה תתקבל בהצלחה נציג את התוצאה ב – alert, במימוש הספציפי של ה – handler אפשר להשתמש רק במאפיין responseText מכיוון שלא שלחנו XML אלא רק מחרוזת, בסופו של דבר אנחנו קוראים למתודת send, וזאת התוצאה:

pic1.jpg

POST

אפשר כמובן גם לשלוח בקשת Request בתצורת POST, הקוד יהיה כמעט זהה:

function RunPOST() {

    var num1 = document.getElementById('txt1').value;

    var num2 = document.getElementById('txt2').value;

 

    var xhr = new XMLHttpRequest();

 

    var url = "Add2Numbers.ashx";

    xhr.open("POST", url, true);

 

    xhr.onreadystatechange = function() {

        if (xhr.readyState == 4) {

            alert(xhr.responseText);

        }

    };

 

    var contentType = "application/x-www-form-urlencoded";

    xhr.setRequestHeader("Content-Type", contentType);

    var qs = 'n1=' + num1 + '&n2=' + num2;

    xhr.send(qs);

}

 

השינויים בין הקריאות:

o       ה – URL מכיל רק את הכתובת בלי הפרמטרים.

o       ה – method הוגדר כ – POST

o       הוספנו הגדרה ל – header מסוג Content-Type שהערך שלו זה application/x-www-form-urlencoded

o       הערכים נשלחו בזמן קריאה ל – send.

 

הפעלת מתודות של Web Service (הקוד המלא - NativeAJAX\GetHtmlOfOtherPage\WebService)

          GET

הקוד שהרצנו מקודם עובד אבל יש לו חסרון אחד, זה לא נוח ולא הגיוני "לגלוש" ל – handler כדי לקבל תשובות, באמצעות Web Service נוכל להפעיל מתודות בצד השרת ולקבל תשובות.

נגדיר WebService חדש שמקבל שני מספרים ומחזיר מספר.

 

 

[WebService(Namespace = "http://tempuri.org/")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

[System.ComponentModel.ToolboxItem(false)]

public class Add2NumberService : System.Web.Services.WebService

{

    [WebMethod]

    public int Add(int n1, int n2)

    {

        return n1 + n2;

    }

}

חשוב לשים לב שלמחלקה Add2NumberService יש attribute (מה זה Attribute) בשם WebService ולמתודה Add יש attribute בשם WebMethod.

כעת אנו רוצים להפעיל את המתודה Add מתוך קוד ה – Java Script.

כברירת מחדל Web Service חושף את עצמו רק ב – POST ו – SOAP, כדי לחשוף אותו גם ב – GET, צריך להוסיף את המקטע הבא לקובץ הקונפיג

<webServices>

  <protocols>

    <add name="HttpGet"/>

  </protocols>

</webServices>

 

כעת נכתוב את הקוד הבא. (בפונקצית Run)

var xhr = new XMLHttpRequest();

 

var qs = '?n1=' + num1 + '&n2=' + num2;

var url = "Add2NumberService.asmx/Add" + qs;

xhr.open("GET", url, true);

 

xhr.onreadystatechange = function() {

    if (xhr.readyState == 4 && xhr.status == 200) {

        // extract the value from result

    }

};

 

xhr.send()

 

אפשר לראות שה – URL מכיל את כתובת ה – service ואת שם המתודה ומקבל את הערכים ב – query string.

אבל, כדי להוציא את המידע כעת צריך לעשות קצת עבודה, ה – responseText יחזיר לנו את התוצאה הבאה:

pic2.jpg

ולכן כאן צריך להשתמש עם ה – responseXML כדי לחלץ את המידע מכיוון ש – Web Service  מחזירים כברירת מחדל XML, הסיבה שנעדיף לעבוד עם Web Service ולא עם handler היא פשוטה, זה נכון שחילוץ המידע פשוט יותר ב – handler, אבל handler יכול להחזיר רק מחרוזות ו – html ואילו Web Service יכול להחזיר אובייקטים וזה הגיוני להפעיל מתודה בשרת כדי לקבל את המידע, ולא לקבל html.

                   POST

כמו ב – handler קריאה ל – Web Service ב – POST דומה מאוד לקריאה ב – GET, והיא תראה כך:

var xhr = new XMLHttpRequest();

 

var url = "Add2NumberService.asmx/Add";

xhr.open("POST", url, true);

 

xhr.onreadystatechange = function() {

    if (xhr.readyState == 4 && xhr.status == 200) {

        alert(xhr.responseXML.text);

    }

};

 

var contentType = "application/x-www-form-urlencoded"

xhr.setRequestHeader("Content-Type", contentType);

var qs = 'n1=' + num1 + '&n2=' + num2;

xhr.send(qs);

 

 

סיכום ביניים

עבודה ב – AJAX משמעותה שליחת בקשות לשרת מתוך Java Script, קבלת הנתונים במהירות ועבודה על הנתונים בצד הלקוח.

ראינו כיצד להשתמש באובייקט XmlHttpRequest, ראינו עבודה עם Http Handler ועבודה עם Web Service ועשינו זאת ב – GET ו – POST.

 

תרגיל – קבלת רשימת ערים לפי מדינות

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

·        צרו דף שבו יהיו שני אלמנטים מסוג select.

·        ל – select הראשון יהיה רשימת מדינות, ובשני לא יהיה כלום (כרגע)

·        צרו Web Service שיש בו מתודה שמקבלת string (שם מדינה) ומחזירה מערך של string (רשימת ערים)

·        הירשמו לאירוע onchange של ה – select הראשון הפעילו את ה – Web Service ושילחו למתודה את המדינה שהמשתמש בחר, קבלו בחזרה את רשימת הערים ומלאו אותם ב – select השני, (זכרו לרוקן את ה – select השני מכל הערכים שיש בו)

·        מומלץ לשים break point במקום שבו מקבלים את התוצאה ולהסתכל מה מכיל האובייקט responseXML כדי לדעת כיצד לפרסר את הנתונים שמתקבלים. (במידה וה – debugger של visual studio עושה בעיות ולא מוכן להציג את התוכן של המאפיין תשתמשו ב – debugger של IE. בקשה על F12 תפתח את החלון של IE Developer Tool Bar, שם בחרו את הטאב Script ולחצו על Start Debugging ותוכלו לשים break points כדי לדבג)

פיתרון התרגיל (הקוד המלא נמצא ב - NativeAJAX\CountriesAndCities)

לפני קריאת הפיתרון מומלץ בחום לנסות לפתור לבד.

 

צד  השרת:

[WebService(Namespace = "http://tempuri.org/")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

[System.ComponentModel.ToolboxItem(false)]

public class Country : System.Web.Services.WebService

{

    [WebMethod]

    public string[] GetCities(string country)

    {

        if (country.ToLower() == "israel")

        {

            return new string[]

                {

                    "Tel-Aviv",

                    "Ramt Gan",

                    "Elad"

                };

        }

        else if (country.ToLower() == "usa")

        {

            return new string[]

                {

                    "Boston",

                    "Los Angeles",

                    "Holywood"

                };

        }

 

        return new string[0];

    }

}

 

 

 

צד הלקוח:

<select onchange="Fill(this.value)">

    <option>Select</option>

    <option value="israel">Israel</option>

    <option value="usa">USA</option>

</select>

<select id="city">

</select>

function Fill(value) {

 

    var cityCombo = document.getElementById('city');

    cityCombo.options.length = 0;

 

    if (value != '-1') {

        var xhr = new XMLHttpRequest();

 

        var qs = '?country=' + value;

        var url = "Country.asmx/GetCities" + qs;

        xhr.open("GET", url, true);

 

        xhr.onreadystatechange = function() {

            if (xhr.readyState == 4 && xhr.status == 200) {

 

                var arrayNode = xhr.responseXML.childNodes[1];

 

                for (var i = 0; i < arrayNode.childNodes.length; i++) {

                    var opt = new Option(arrayNode.childNodes[i].text);

                    cityCombo.options[i] = opt;

                }

            }

        };

 

        xhr.send();

    }

}

 

הכרת פורמט JSON

כמו שראיתם המידע שחוזר מה – Web Service הינו בפורמט XML, בפיתרון התרגיל קבלנו את ה – XML הבא:

puc3.jpg

 

וכמו שאפשר לשים לב XML הוא פורמט מבוזבז, כל הטקסט של התגים עצמם נמצא גם לפני התוכן וגם אחרי התוכן, בנוסף כמו שאפשר לראות הקוד שצריך לכתוב כדי לחלץ את המידע מתוך ה – XML הוא מאוד לא ברור ומאוד לא קריא, בתרגיל היה מדובר על מערך של מחרוזות, תחשבו איך יראה ה – XML של מערך של מחלקה שיש לה כמה מאפיינים שחלקם מערכים של מחלקות אחרות וכו', הקוד שנצטרך לכתוב כדי לחלץ את התוכן יהיה כמובן מאוד לא ברור וקשה לתחזוקה.

כדי לפתור את שני הבעיות הללו נוצר פורמט אחר שנקרא JavaScript Object Notation המוכר כ – JSON, לא נרחיב הרבה על הפורמט, בקצרה במקום לשלוח את ה – XML הבזבזני נוכל את אותו תוכן בפורמט JSON והוא יראה כך:

["Tel-Aviv","Ramt Gan","Elad"]

ב – JSON זה אומר מערך של מחרוזות, במקרה של אובייקט, כל מאפיין ייכתב כך:

key:value

בשיטה הזאת נוכל לחסוך בתעבורה של ה – XML שהוא בזבזני, אבל יותר חשוב מכך אפשר להפוך מאפיינים ב – Java Script מחרוזת בפורמט JSON לאובייקט, ואז נוכל להשתמש במאפיינים של האובייקט כדי לקבל גישה למידע במקום לרוץ על ה- XML.

 

קבלת אובייקט ושליחת פרמטרים ב – JSON

יש לנו שני דרכים ב – Web Service לקבל ולשלוח ב – JSON, הראשונה היא בעזרת מחלקה מיוחדת שנקראת JavaScriptSerializer והדרך השנייה היא Script Service, לכל אחד מהם יש חסרונות ויתרונות ונכיר כאן את שניהם.

עבודה עם JavaScriptSerializer (הקוד המלא - NativeAJAX\JSON\JSS)

נניח שיש לנו מחלקת Person שנראית כך:

public class Person

{

    public int Id { get; set; }

    public string Name { get; set; }

    public float Age { get; set; }

    public List<Address> Addresses { get; set; }

}

 

public class Address

{

    public string Country { get; set; }

    public string City { get; set; }

    public string Street { get; set; }

    public int Number { get; set; }

}

 

ויש לנו מתודה שאמורה להחזיר מופע שלו (בשלב הזה מתודה שלא מקבלת פרמטר)

אם נחזיר XML נקבל משהו בסגנון הזה:

puc4.jpg

 

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

נכתוב את הקוד הבא:

[WebMethod]

public string GetPerson()

{

    var person = GetPersonInstance();

    return new JavaScriptSerializer().Serialize(person);

}

 

 

 

 

 

 

כעת כשנפנה ל – Web Service מצד הלקוח נקבל את המחרוזת הבאה:

pic5.jpg

 

כבר אפשר לראות שחסכנו די הרבה תעבורה, אבל מה שיותר חשוב היות שהתוכן של התשובה היא בפורמט JSON ולכן בצד הלקוח נוכל לכתוב את הקוד הבא:

xhr.onreadystatechange = function() {

    if (xhr.readyState == 4 && xhr.status == 200) {

        var obj = eval('(' + xhr.responseXML.text + ')');

    }

};

 

 

הפעלה של המתודה eval על מחרוזת בפורמט JSON יוצרת אובייקט של JS, ולמעשה נוכל להשתמש בכל המאפיינים שיש לו, כמו שאפשר לראות בתמונה הבאה:

pic6.jpg

 

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

(יש דרכים נוספות להמרת JSON לאובייקטים, למשל JSON.parse)

                  

כעת נניח שיש לנו מתודה שנקראת Update והיא אמורה לקבל כפרמטר מופע של  Person ולשמור את השינויים שנעשו בו, השאלה שנשאלת כיצד נוכל לשלוח אובייקט לשרת, בעזרת JavaScriptSerializer נוכל לעשות זאת בפשטות, נגדיר את המתודה הבאה.

 

[WebMethod]

public void Update(string personInJsonFormat)

{

    var serializer = new JavaScriptSerializer();

    Person person =

        serializer.Deserialize<Person>(personInJsonFormat);

 

    // save changes....

}

 

 

כמו שאפשר לראות המתודה מקבלת מחרוזת וממירה אותו לאובייקט מסוג Person.

מה שיותר צריך להבין כיצד בצד הלקוח נמיר את האובייקט למחרוזת JSON, כדי שנוכל לשלוח אותו לשרת, (כעת מומלץ לעבוד ב – POST כדי שלא נעבור את מספר התוויים המקסימאלי שיכול להיות ב – Query String)

שלב ראשון נייבא את האובייקט מצד השרת בעזרת קריאה למתודה הקודמת (זאת שמשתמשת ב – eval כדי להמיר מ – JSON לאובייקט) לאחר מכן נשמור אותו במשתנה גלובלי, ואז נפעיל את המתודה הבאה:

function RunUpdate() {

 

    obj.Name = "Update Name";

 

    var xhr = new XMLHttpRequest();

    var url = "PersonService.asmx/Update";

    xhr.open("POST", url, true);

 

    var contentType = "application/x-www-form-urlencoded";

    xhr.setRequestHeader("Content-Type", contentType);

 

    var qsValue = JSON.stringify(obj);

    xhr.send("personInJsonFormat=" + qsValue);

}

 

נשנה מאפיינים של האובייקט, נגדיר את השליחה ב- POST (ולכן גם נגדיר את ה – Request Header), נשתמש בפונקציה stringify כדי להמיר את האובייקט ל – JSON, ונקבל את המחרוזת הבאה:

{"Id":1,"Name":"Update Name","Age":20.2,"Addresses":[{"Country":"Israel","City":"Elad","Street":"Amos","Number":19},{"Country":"Israel","City":"Beni-Brak","Street":"noam","Number":10}]}

נשלח אותה ב – body של ה – Request כערך לפרמטר personInJsonFormat.

 

אפשר לראות ששליחה וקבלה של אובייקטים ב – JSON הופכת להיות קלה מאוד, בצד השרת נשתמש ב – JavaScriptSerializer ובצד הלקוח נשתמש ב – eval וב – stringify.

 

שימוש ב – ScriptService כדי לשלוח ולקבל JSON (הקוד המלא - NativeAJAX\JSON\SS)

אם הכול כל כך נחמד ופשוט בעזרת JavaScriptSerializer אז למה לא להשתמש בזה תמיד ?

התשובה פשוטה – ראשית לא קבלנו באמת JSON, חדי העין יכלו לשים לב שקבלנו XML שהתוכן שלו היה JSON, אבל זה לא כל כך משנה, מה שכן מפריע הוא שהחתימה של המתודה יקבלו ויחזירו תמיד מחרוזות במקום אובייקטים, ובדרך כלל Web Services לא משמשים רק לקריאות Java Script אלא גם לעבודה בצד השרת ואם המתודה תחזיר מחרוזת, מי שיפעיל אותה מאפליקציה אחרת לא ידע (או לא אמור לדעת) מה לעשות עם המחרוזת שהוא מקבל, וכמובן הוא לא ידע מה לשלוח לה כפרמטר.

כדי לפתור את בעיית "חתימת המתודות" נוכל להשתמש במנגנון של ScriptService, כל מה שנצטרך לעשות זה להוסיף ל – Web Service שלנו Attribute בשם ScriptService ולמתודות שלנו Attribute בשם ScriptMethod, ברגע שנעשה זאת המתודות שלנו יוכלו לקבל ולשלוח אובייקטים ומאחורי הקלעים תתבצע ההמרה מ - JSON ול – JSON.

ב – Web Service יהיה לנו את אותם שני מתודות אבל הפעם הם יקבלו ויחזירו Person

 

 

 

 

 

 

 

[WebService(Namespace = "http://tempuri.org/")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

[System.ComponentModel.ToolboxItem(false)]

[ScriptService]

public class PersonServiceWithSS : System.Web.Services.WebService

{

    [ScriptMethod(UseHttpGet = true)]

    [WebMethod]

    public Person GetPerson()

    {

        Person person = GetPersonInstacne();

 

        return person;

    }

 

 

    [ScriptMethod]

    [WebMethod]

    public void Update(Person person)

    {

        // save changes....

    }

}

 

לפני שנעבור לצד הלקוח חשוב לשים לב שבמתודה GetPerson הוגדר ב – Attribute ש – UseHttpGet הוא true, היות שברגע שהשתמשנו ב –ScriptService אפשר להפעיל את המתודות רק ב – POST אלא אם כן נגדיר אחרת.

כעת נעבור לצד הלקוח.

כדי לקבל את האובייקט נריץ את הקוד הבא

var xhr = new XMLHttpRequest();

 

var url = "PersonServiceWithSS.asmx/GetPerson";

xhr.open("GET", url, true);

 

xhr.onreadystatechange = function() {

    if (xhr.readyState == 4 && xhr.status == 200) {

        obj = eval('(' + xhr.responseText + ')');

    }

};

 

xhr.setRequestHeader("Content-Type", "application/json");

xhr.send();

 

אפשר לראות שבשונה מהקריאה הקודמת (שימוש ב – JavaScriptSerializer) אנחנו מפעילים eval על responseText ולא gל responseXML.text מכיוון שחזר JSON אמיתי ולא XML שמכיל JSON, בנוסף היינו צריכים להגדיר את ה – requestHeader ל – JSON (אע"פ שמדובר ב – GET), היתרון הגדול הוא כמובן שהמתודה מחזירה אובייקט ולא מחרוזת כך שאפליקציות אחרות יכולות להשתמש בה כרגיל.

אם נסתכל מה מכיל ה – obj שקבלנו לאחר הפעלת eval נראה את הדבר הבא:

pic7.jpg

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

כדי לעדכן את האובייקט נכתוב את הקוד הבא:

obj.d.Name = "Update Name";

 

var xhr = new XMLHttpRequest();

var url = "PersonServiceWithSS.asmx/Update";

xhr.open("POST", url, true);

 

xhr.setRequestHeader("Content-Type", "application/json");

 

var qsValue = JSON.stringify(obj.d);

xhr.send('{person:' + qsValue + '}');

 

בהתחלה נעדכן את המאפיינים שנרצה (עם שימוש ב – d כדי לגשת למאפיינים), לאחר מכן נפעיל את stringify על obj.d ונשלח אותו כפרמטר לפונקציה, כמובן שצריך לשלוח אותו בפורמט JSON כלומר:

{key:value}

ולכן נכתוב:

{person: personvalue}

כש – personValue יתחלף עם מה שקבלנו מה – stringify.

סיכום ביניים

שימוש בפורמט JSON חוסך בתעבורה ועוזר לכתוב קוד נקי וקל לתחזוקה, כדי לעבוד עם JSON אפשר להשתמש ב – JavaScriptSerializer בצד השרת ובמקרה הזה המתודות יכולים לקבל ולהחזיר רק מחרוזות, אופציה שנייה היא להשתמש ב – ScriptService ובמקרה הזה צריך להגדיר את ה – content-type ל – application/json, בנוסף כדי לעבוד ב – GET צריך להגדיר את זה במפורש בעזרת UseHttpGet.

 

תרגיל – Auto Complete Text Box (הפיתרון המלא - NativeAJAX\AutoComplete)

בשלב הזה כדאי לעצור את המשך הקריאה ולתרגל, אנו רוצים שיהיה לנו תיבת טקסט שבזמן הקשת אותיות נקבל הצעות משלימות (כמו בגוגל) נשתמש ב – JSON בשיטת ScriptService.

         

·        צרו דף והוסיפו תיבת טקסט.

·        הירשמו לאירוע onkeyup ושלחו כפרמטר לפונקציה את הערך שיש בתיבת הטקסט.

·        הוסיפו select והגדירו את ה – size שלו ל – 7.

·        הירשמו לאירוע onchange של ה – select.

·        צרו Web Service עם מתודה המקבלת מחרוזת ומחזירה מערך. (לא לשכוח להוסיף את ה – attributes המתאימים), המתודה תחזיר מערך של מחרוזות אקראיים שמתחילים עם הטקסט שקבלתם.

·        מתוך המתודה שנרשמתם בזמן onkeyup הפעילו את ה – Web Service ומלאו את ה – select עם הערכים שקבלתם.

·        מתוך המתודה שנרשמתם אליה ב – onchange קחו את הערך שנבחר והשימו אותו בתיבת הטקסט.

 

מומלץ לנסות לפתור לבד את התרגיל, לפני עיון בקוד הפיתרון.

 

 

 

 

 

 

צד השרת:

[WebService(Namespace = "http://tempuri.org/")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

[System.ComponentModel.ToolboxItem(false)]

[ScriptService]

public class AutoComleteService : System.Web.Services.WebService

{

    private static Random random = new Random();

 

    [ScriptMethod(UseHttpGet = true)]

    [WebMethod]

    public List<string> GetSuggestion(string prefix)

    {

        List<string> list = new List<string>();

 

        var count = random.Next(5, 20);

 

        for (int i = 0; i < count; i++)

        {

            list.Add(prefix + RandomString(random.Next(0, 10)));

        }

 

        return list;

    }

 

    private string RandomString(int size)

    {

        // return random string

    }

}

 

 

 

 

 

 

 

 

 

 

 

צד הלקוח:

<input type="text"

onkeyup="GetData(this.value)"

id="txt" />

 

<select id='sp'

        onchange="Change(this)"

        size="7"

        name="sp">

</select>

 

 

 

 

function GetData(value) {

 

    var xhr = new XMLHttpRequest();

 

    var webServiceName = 'AutoComleteService.asmx/GetSuggestion';

    var url = webServiceName + "?prefix='" + value + "'";

 

    xhr.open("GET", url, true);

 

    xhr.onreadystatechange = function() {

        if (xhr.readyState == 4 && xhr.status == 200) {

 

            var arr = eval('(' + xhr.responseText + ')');

 

            var select = document.getElementById('sp');

            select.options.length = 0;

            for (var i = 0; i < arr.d.length; i++) {

                select.options[i] = new Option(arr.d[i]);

            }

        }

    };

 

    xhr.setRequestHeader("Content-Type", "application/json");

    xhr.send();

}

 

function Change(select) {

    var data = document.getElementById('txt');

 

    data.value = select.options[select.selectedIndex].text;

}

 

 

 

 

עבודה עם ICallbackEventHandler

כעת אחרי שראינו והבנו את העבודה עם AJAX הבסיסי, נכיר כמה מן העטיפות לעבודה עם XmlHttpRequest, העטיפה הראשונה שנכיר היא מימוש ICallbackEventHandler

התפקיד של ה – Interface הזה הוא לאפשר הפעלה של מתודה בצד השרת מצד הלקוח בלי צורך לכתוב לבד את כל הקוד של XmlHttpRequest, ובסיום עבודת השרת להפעיל מתודה בצד הלקוח עם התשובה מהשרת.

נניח שיש לנו תיבת טקסט של רישום משתמש חדש, מי שרוצה להירשם מתחיל לכתוב את השם שלו בתיבת הטקסט במידה והשם קיים כבר במערכת תיבת הטקסט נצבעת באדום, וכל זה מבלי לעשות Post Back או לכתוב קוד עם XmlHttpRequest.

ה – Interface מכיל שני מתודת שצריך לממש.

void RaiseCallbackEvent(string eventArgument);

string GetCallbackResult();

 

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

התהליך הוא כזה.

·        בצד ה – Java Script צריך לקרוא למתודה מיוחדת שנקראת WebForm_DoCallBack שמקבלת את הפרמטרים הבאים.

o       ה – Control שמממש את ICallbackEventHandler.

o       מתודה ב – Java Script להפעלה כדי לקבל את הפרמטר למתודת RaiseCallbackEvent בצד השרת.

o       שם של מתודה להפעלה ב – Java Script במידה והכול עבר בהצלחה.

o       שם של מתודה להפעלה ב – Java Script במידה והיה שגיאה.

o       משתנה מסוג boolהאם הקריאה היא סינכרונית או לא.

·        לאחר שקראנו ל – WebForm_DoCallBack

·        המתודה שאמורה להחזיר את הפרמטר עבור RaiseCallbackEvent נקראת.

·        Page_Load (וכל ה – Page Life Cycle) מופעל. (בצד השרת)

·        RaiseCallbackEvent נקראת.

·        GetCallbackResult נקראת.

·        המתודה בצד הלקוח מופעלת (או מתודת ההצלחה או מתודת הכישלון)

 

 

 

נראה את הקוד. (הקוד המלא – Callback\ WebPageWithCallback.aspx)

צד הלקוח:

<input type="text" id="txt" runat="server" />

 

function GetArgs() {

    return document.getElementById('txt').value;

}

 

function onSuccess(res) {

    var txt = document.getElementById('txt');

 

    if (res == "true") {

        txt.style.backgroundColor = 'red';

    }

    else {

        txt.style.backgroundColor = '';

 

    }

}

 

function onFailed(res) {

    alert(res);

}

 

צד השרת:

הדף צריך לממש את ICallbackEventHandler

private string userName;

 

#region ICallbackEventHandler Members

 

public void RaiseCallbackEvent(string eventArgument)

{

    userName = eventArgument;

}

 

public string GetCallbackResult()

{

    if (userName == "shlomo")

    {

        return "true";

    }

 

    return "false";

}

 

#endregion

 

מה שאנו עושים הוא די פשוט, המתודה RaiseCallBackEvent תקבל את הטקסט שהמשתמש יקליד ותשמור אותו במשתנה.

לאחר מכן המתודה GetCallBackResult תבדוק האם הוא קיים (בדוגמה אין לנו בסיס נתונים – ולכן נבדוק מול השם shlomo) ותחזיר תשובה מתאימה.

כדי לקשר בין תיבת הטקסט לבין קריאה ל – WebForm_DoCallBack נצטרך לכתוב את הקוד הבא: (ב – Page_Load)

 

protected void Page_Load(object sender, EventArgs e)

{

    string callBackStr =

        ClientScript.GetCallbackEventReference(this,

                                                "GetArgs()",

                                                "onSuccess",

                                                null,

                                                "onFailed",

                                                true);

    txt.Attributes["onkeyup"] = callBackStr;

}

 

השורה הראשונה עושה שני דברים:

·        דואגת שקובץ הסקריפט שמכיל את הפונקציה WebForm_DoCallBack תרד ללקוח.

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

o       this – הפקד שמממש את ICallbackEventHandler

o       GetArgs() – פונקציה בצד הלקוח שתחזיר את הערך מתוך תיבת הטקסט.

o       onSuccess – שם הפונקציה להפעלה במידה והכול הצליח.

o       onFailed - שם הפונקציה להפעלה במידה ומשהו נכשל.

o       true . – שהקריאה תהיה אסינכרונית.

 

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

כעת במידה והטקסט יהיה shlomo תיבת הטקסט תצבע באדום, אחרת היא תהיה לבנה.

pic8.jpg

 

תרגיל – מימוש מחשבון בעזרת ICallbackEventHandler
(את הפיתרון המלא ניתן למצוא ב –
Callback\ WebForm1.aspx)

 

עליכם לממש מחשבון שיתמוך בארבעת הפעולות הבסיסיות (חיבור חילוק כפל וחילוק).

המחשבון יהיה UserControl שיכיל את ה – GUI ואת המתודות לחישוב.

בדף יהיה לכם מופע של ה – User Control.

פתרון: (כרגיל מומלץ לנסות לפתור לבד אץ התרגיל ואז להסתכל בפיתרון המוצע)

 

צד הלקוח: (User Control)

 

<input type="text" id="txt1" />

<input type="text" id="txt2" />

 

<input type="button" value="Add" id="btnAdd" runat="server" />

<input type="button" value="Sub" id="btnSub" runat="server" />

<input type="button" value="Multi" id="btnMulti" runat="server" />

<input type="button" value="Div" id="btnDiv" runat="server" />

 

<span id="sp"></span>

 

 

function GetArgs() {

    var num1 = document.getElementById('txt1').value;

    var num2 = document.getElementById('txt2').value;

    var method = event.srcElement.value;

 

    // 2:4:Add

    return num1 + ':' + num2 + ':' + method;

}

 

function onSuccess(res) {

    document.getElementById('sp').innerHTML = res;

}

 

function onFailed(res) {

    alert(res);

}

 

 

צד השרת (User Control)

 

protected void Page_Load(object sender, EventArgs e)

{

    string callBackStr =

        ClientScript.GetCallbackEventReference(this,

                                                "GetArgs()",

                                                "onSuccess",

                                                null,

                                                "onFailed",

                                                true);

 

    btnAdd.Attributes["onclick"] = callBackStr;

    btnSub.Attributes["onclick"] = callBackStr;

    btnMulti.Attributes["onclick"] = callBackStr;

    btnDiv.Attributes["onclick"] = callBackStr;

}

 

int num1, num2;

string method;

 

#region ICallbackEventHandler Members

 

public void RaiseCallbackEvent(string eventArgument)

{

    // 2:4:Add

    string[] args = eventArgument.Split(':');

 

    num1 = int.Parse(args[0]);

    num2 = int.Parse(args[1]);

    method = args[2];

}

 

public string GetCallbackResult()

{

    if (method == "Add")

        return (num1 + num2).ToString();

 

    if (method == "Sub")

        return (num1 - num2).ToString();

 

    if (method == "Multi")

        return (num1 * num2).ToString();

 

    if (method == "Div")

        return (num1 / num2).ToString();

 

    return string.Empty;

}

 

#endregion

היכרות עם ScriptManager

ראינו עד עכשיו עבודה עם AJAX בצורה עצמאית ועבודה עם ICallbackEventHandler, עבודה עם AJAX בצורה עצמאית היא לא נוחה ועבודה עם ICallbackEventHandler היא ישנה וכמובן שזה לא הגיוני לשלוח ולקבל רק מחרוזות ובנוסף בכל ריצה לשרת יש הפעלה של Page_Load (אע"פ שהמשתמש לא שם לב לזה).

ScriptManager הוא התשובה העדכנית והנכונה לעבודה עם AJAX בסביבתASP.NET בעזרתו נוכל גם להפעיל מתודות בצורה פשוטה וקלה ובנוסף נוכל להחליט על חלקים מהדף שיעבדו עצמאית מול השרת ולא יגרמו ל – Post Back מלא אלא ל – Partial Post Back כפי שנראה בהמשך, כמו כן הוא מכיל הרבה ספריות Java Script שניתן להשתמש בהם וזה יכול לחסוך הרבה עבודה, במדריך זה נתייחס רק לאספקטים לעבודה עם  AJAX ולא לספריות שהוא מביא איתו.

תחילת העבודה

כדי להתחיל לעבוד עם ScriptManager עליכם להוסיף (בתחילת העמוד) את הקוד הבא

<asp:ScriptManager ID="sm1" runat="server"></asp:ScriptManager>

 

בדף יכול להיות רק מופע אחד של ScriptManager וכמו כן זה אמור להופיע בתחילת העמוד (הפקד הראשון לאחר התגית form)

 

הפעלה של WebService בעזרת ScriptManager
(הקוד המלא נמצא ב -
ScriptManager\InvokeWebService)

כדי להפעיל מתודות של WebService בעזרת ScriptManager צריך לרשום את ה – WebService ל – ScriptManager ואז אפשר להשתמש בפונקציות של ה – WebService בצורה רגילה (כאילו הם מתודות שמוגדרות ב – Java Script)

נניח שיש לנו WebService בשם Calc שיש לו את המתודה הבאה:

[ScriptMethod]

[WebMethod]

public int Add(int num1, int num2)

{

    return num1 + num2;

}

 

חשוב כמובן לזכור של – WebService חייב להיות את ה – Attribute של ScriptService (אחרת זה לא יעבוד)

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

<asp:ScriptManager ID="sm" runat="server">

    <Services>

        <asp:ServiceReference Path="~/Calc.asmx" />

    </Services>

</asp:ScriptManager>

 

הרישום של ה – Service יוצר קובץ Java Script שיורד ללקוח ועוטף את הפונקציות הקימות ב – Service

כעת ניתן בצד הלקוח להפעיל את מתודות ה – Service בקריאה למתודה בשם המלא שלה – כלומר – שם ה – namespace שם ה – class ושם המתודה.

 

דוגמא:

<input type="text" id="txt1" />

<input type="text" id="txt2" />

 

<input type="button" value="Add" onclick="Run()" />

 

 

function Run() {

    var num1 = document.getElementById('txt1').value;

    var num2 = document.getElementById('txt2').value;

 

    InvokeWebService.Calc.Add(num1, num2, onSuccess, onFailed)

}

 

function onSuccess(res) {

    alert(res);

}

 

function onFailed(res) {

    alert(res.get_message());

}

 

את המתודה מפעילים בעזרת ה – namespace המלא שם המחלקה ושם המתודה, שולחים למתודה את:

·        הפרמטרים שהיא אמורה לקבל.

·        שם של מתודה להפעלה במידה והכול יצליח.

·        שם של מתודה להפעלה במידה ויהיה exception.

במתודת onSuccess הפרמטר res יהיה ה – return value של הפונקציה, בדוגמא שלנו החזרנו int אבל כמובן שאפשר להחזיר כל אובייקט ולעבוד איתו בצד הלקוח.

במידה ונגיע למתודה onFailed נקבל אובייקט שיכיל מידע אודות השגיאה, בדוגמא השתמשנו ב – res.get_message, שזה מקביל למאפיין Message בצד השרת לאובייקט מסוג exception, האובייקט שנקבל במידה ויש שגיאה בצד השרת מכיל מידע מועיל, הנה חלק מהדברים החשובים:

·        get_message – מחזיר את הודעת השגיאה.

·        get_exceptionType – מחזיר אס סוג השגיאה.

·        get_stackTrace – מחזיר את ה – Stack Trace כמו בצד השרת

·        get_statusCode – מחזיר את מספר השגיאה (HTTP)

 

אפשר לכתוב את המתודה בצורה יותר קריאה (לדעתי) בעזרתanonymous function

InvokeWebService.Calc.Add(num1, num2,

    function (res) {

        alert(res);

    },

    function (res) {

        alert(res.get_message());

    });

 

בנוסף אפשר להחליף את השימוש ב –

document.getElementById('txt1').value;

 

ל –

$get('txt1').value;

 

(הקיצור של $get מגיע מהספריות של ScriptManager.)

 

הפעלה של מתודות בדף (PageMethods) בעזרת ScriptManager
(את הקוד המלא ניתן למצוא
ScriptManager\ InvokePageMethod)

לפעמים זה מרגיש לא נכון לכתוב מתודה ב – WebService רק כדי לפנות אליה ב – AJAX, בדרך כלל במקרים בהם נרצה לפנות לשרת כדי לבצע לוגיקה ששייכת ספציפית לדף שבה נמצאים כרגע, ניקח לדוגמא את הבדיקה האם שם משתמש תפוס שממשנו בעזרת ICallbackEventHandler נניח שנרצה להפעיל אותה בעזרת ScriptManager אבל לא נרצה למקם את הפונקציה בתוך WebService אלא בתוך הקוד של הדף.

נוכל לבצע את זה בעזרת מנגנון מיוחד של ScriptManager שנקרא PageMethods, מה שצריך לעשות זה את השלבים הבאים:

·        להגדיר ל – ScriptManager את המאפיין EnablePageMethod ל – true

·        להגדיר מתודות סטטיות בדף ולתת להם את ה – WebService Attribute

דוגמת קוד

בצד השרת נכתוב את המתודה הבאה:

[WebMethod]

public static bool IsUserExsit(string name)

{

    return name == "shlomo";

}

 

בצד הלקוח נכתוב את הקוד הבא:

<asp:ScriptManager ID="sm1"

                    runat="server"

                    EnablePageMethods="true">

</asp:ScriptManager>

 

<input type="text" id="txt" onkeyup="checkName(this)" />

 

function checkName(txt) {

    PageMethods.IsUserExsit(txt.value,

        function (res) {

            if (res) {

                txt.style.backgroundColor = 'red';

            }

            else {

                txt.style.backgroundColor = '';

            }

        },

        function (res) {

            alert(res.get_message());

        });

}

 

בתוך הפונקציה checkName אנחנו מקבלים את תיבת הטקסט כפרמטר, נפעיל את המתודה IsUserExsit בעזרת PageMethods נשלח כפרמטר את:

·        הערך שיש בתיבת הטקסט.

·        פונקציה להפעלה בזמן הצלחה - הפונקציה מקבלת את res שזה הערך המוחזר.

·        ופונקציה להפעלה בזמן כישלון.

במידה והקוד עם ה - anonymous function מבלבל אתכם, כך זה יראה בצורה האחרת:

function checkName2(txt) {

    PageMethods.IsUserExsit(txt.value, onSuccess, onFailed);

}

 

function onSuccess(res) {

    if (res) {

        $get('txt').style.backgroundColor = 'red';

    }

    else {

        $get('txt').style.backgroundColor = '';

    }

}

 

function onFailed(res) {

    alert(res.get_message());

}

 

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

 

עבודה עם UpdatePanel

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

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

כדי לפתור את זה הומצא הרעיון של Partial Post Back כלומר לעשות Post Back לשרת אבל לא Post Back רגיל אלא חלקי – מה הכוונה – בדוגמא עם הגריד אנחנו יכולים לעטוף את הגריד בפקד מיוחד שנקרא UpdatePanel וכל Post Back שיהיה מתוך ה – UpdatePanel (בדוגמא שלנו – דפדוף בגריד) תפנה לשרת ב – AJAX, השרת יפעיל את Page_Load וכל התהליך הרגיל (עם כמה שינויים) אבל הלקוח יקבל רק את ה – html הרלוונטי לאותו Update Panel, אמנם לא חסכנו ביצועים מול השרת אבל נתנו חווית משתמש נעימה יותר.

מה שצריך לעשות זה בסך הכול לעטוף את מה שנרצה ב – Update Panel בצורה הבאה. (את הדוגמא המלאה עם הדפדוף ניתן למצוא ב – UpdatePanelDemo/ Pageing.aspx

כמובן שצריך להוסיף ScriptManager בעמוד.

<asp:UpdatePanel ID="udp1" runat="server">

    <ContentTemplate>

        <!-- Controls that do PostBack -->

    </ContentTemplate>

</asp:UpdatePanel>

 

כשתריצו את הדוגמא של ה – Paging תגלו חוויית משתמש שלא הכרתם בזמן הדפדוף.

יש המון מה לדבר על Update Panel אני לא ארחיב כאן במדריך, אני רק אציין כמה נקודות שצריך ללמוד אותם ולשים לב אליהם.

·        אי אפשר לשנות בעמוד נתונים מחוץ ל – Update Panel במידה וה – Post Back התרחש מתוך ה – Update Panel, נניח שיש לחצן בתוך Update Panel ותיבת טקסט מחוץ ל – Update Panel, במידה ובקוד של הלחצן (בצד השרת) ננסה לשנות את התוכן של תיבת הטקסט, כשילחצו על הלחצן התוכן של תיבת הטקסט לא ישתנה.

·        במידה ויש כמה  Update Panel בעמוד כל Post Back של אחד מה – Update Panel מעדכן את כולם אלא אם כן המאפיין UpdateMode (האחרים – לא זה שמתוכו נהיה Post Back) הוגדר כ – Conditional ואז כדי לעדכן אותו כשה – Post Back הוא מ – Update Panel  אחר צריך לקרוא למתודת Update שלו. יש בכל הנושא הזה (מתי Update Panel מתעדכן) הרבה כללים, לקריאה נוספת.

·        אי אפשר להשתמש ב – FileUpload בתוך UpdatePanel (רק בעזרת הפקד של AjaxControlToolKit – נראה בהמשך)

·        יש נושא שנקרא Trigger שמאפשר ל – Update Panel לעשות Post Back מלא אם הוא צריך או לאפשר ל – Update Panel להתעדכן כש – Update Panel  אחר עושה Post Back גם אם הוא הוגדר כ – Conditional.

 

 

 

 

 

 

היכרות עם AjaxControlToolKit

ספריית הפקדים שמגיעה עם ASP.NET לעבודה עם AJAX היא די קטנה, יש את ה – Script Manager ואת Update Panel ועוד שני פקדים שכמעט ולא משתמשים בהם
(
Update Progress ו - Timer), כדי לפצות על כך יש למיקרוסופט ב – CodePlex פרויקט שנקרא   AjaxControlToolKitהפרויקט הוא אוסף נרחב של פקדים שעובדים ב – AJAX, אחד הדוגמאות הטובות ביותר היא הפקד Calendar של ASP, הוא אחד הפקדים ה"לא טובים" ביותר של מיקרוסופט מכיוון שכל בחירה בתאריך או תזוזה של חודש גורמת ל – Post Back מלא, התחליף שלו ב – AjaxControlToolKit הוא מדהים אין Post Back בכלל והכול קורא ב – AJAX. אפשר להסתכל על כל הפקדים שלו ולהבין איך עובדים איתם באתר הדוגמאות שלהם.

 

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

·        הורדת ה  - dll מהאתר שלהם (בלינק למעלה)

·        הוספת הקוד הבא בקונפיג:

<add namespace="AjaxControlToolkit"

    assembly="AjaxControlToolkit"

    tagPrefix="ajax" />

 

·        במידה והורדתם את הגרסה החדשה ביותר אי אפשר להשתמש ב – ScriptManger הרגיל אלא צריך להשתמש בתחליף שלהם ולכתוב בכל מקום את הקוד הבא:

<ajax:ToolkitScriptManager

 

 

 

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

 

שלמה גולדברג

Asp Social Network Controls (Facebook, Twitter etc)

 

איך להוסיף Like ו - Tweet בקלות.

 
הדוקמנטצייה המלאה על לחצן ה - Like כאן.
 
הדוקמנטצייה המלאה על לחצן Tweet כאן.
 
 
כתבתי asp controls שעוטפים את כל המאפיינים של אותם לחצנים, ניתן להוריד מ - Code Plex
 
 
דוגמא ל - Like
 

<%@ Register Assembly="SocialNetwork" Namespace="SocialNetwork.Facebook" TagPrefix="sw" %>

<sw:FacebookLike runat="server"

    UseCurrentPage="true"

    Culture="he-IL"

    FacebookFont="Verdana"

    Layout="Standard"

    Mode="IFrame"

    Scheme="Light"

    ShowFaces="True" />

 
 
דוגמא ל - Tweet
 

<%@ Register Assembly="SocialNetwork" Namespace="SocialNetwork.Twitter" TagPrefix="sw" %>

 

<sw:TweetButton  runat="server"

    Layout="Vertical"

    Mode="JavaScript"

    Text="My Text"

    UseCurrentPage="True"

    Via="account">

    <Related>

        <sw:Related Account="account1" Summary="summary" />

        <sw:Related Account="account2" />

    </Related>

</sw:TweetButton



קבלת תמונה בקובץ שהוגדר כ - embedded ב - Custom Control

כיצד להגדיר תמונה ב - Custom Control כשקובץ התמונה הוגדר כ - embedded.

 
 
הקוד עצמו הוא די פשוט:
 

string rn = "assemblyName.folderName.fileName.bmp";

Type type = typeof(YourControl);

string url = Page.ClientScript.GetWebResourceUrl(type, rn);

 
כשיש לכם את ה - url תוכלו להוסיף אובייקט img ולהגדיר את ה - src ל - url שקבלתם


Posted: Oct 17 2010, 03:13 PM by Shlomo | with 1 comment(s)
תגים:,

video to thumbnail

 

אפליקציה ליצירת תמונה מתוך frame

 
 
נוצר פרוייקט ב - Code Plex. ותוכלו להוריד את האפליקציה מכאן
 
video2thumbnail

Width Height and Duration of wmv file

 

כיצד אפשר לקבל מידע על גובה רוחב וזמן (בשניות) של קובץ wmv

 
כדי לעבוד בקוד עם קבצי wmv צריך להוסיף reference ל - Interop.WMPLib.dll (תוכלו להוריד אותו מכאן, אם אתם לא מוצאים את זה במחשב שלכם)
 
לאחר מכן תוכלו לכתוב את הקוד הבא
 

public class VideoProperties

{

    public int Width { get; set; }

    public int Height { get; set; }

    public long Duration { get; set; }

 

    public VideoProperties(string fileName)

    {

        WindowsMediaPlayerClass wmp = new WindowsMediaPlayerClass();

        IWMPMedia mediaInfo = wmp.newMedia(fileName);

        wmp.currentMedia = mediaInfo;

        Thread.Sleep(1000);

 

        Width = mediaInfo.imageSourceWidth;

        Height = mediaInfo.imageSourceHeight;

        wmp.stop();

 

        Duration = (long)mediaInfo.duration;

 

        wmp.close();

    }

}

 
למעשה את אורך הסרט לקבל אחרי השורה השנייה של ה - ctor, אבל כדי לקבל את הרוחב והגובה חייבים להגדיר את ה - currentMedia אחרת מקבלים 0, הסיבה לעצירת התוכנית למשך שנייה היא לתת זמן לסרט להיטען.
 
נקודה חשובה: במידה ותעשו את זה ב - win form application אתם חייבים להוריד את ההגדרה STAThread מעל ה - main.
 
בפוסט הבא אני אסביר איך אפשר להתמודד עם הבעייה הזאת במידה וצריך בכל זאת את ה - attribute (כדי לפתוח dialog למשל)

Set Image size in html

כיצד להגדיר נכון את גודל התמונה ב - html

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

 

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

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


Posted: Oct 12 2010, 10:22 AM by Shlomo | with 2 comment(s)
תגים:,

Change dimensions of a picture from byte array

כיצד לשנות גודל של תמונה המתקבלת כמערך של בתים

נניח שיש לכם HttpHandler שמציג תמונה ממערך בתים, כדי לשנות את גודלו תוכלו בדרך כלל לכתוב קוד כזה

 

<img src="myhandler.ashx?id=5" width="150" />

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

הקוד הבא יהיה שימושי.

byte[] image = GetImage();

int size;

if (int.TryParse(context.Request["size"], out size))

{

    using (MemoryStream msToRead = new MemoryStream(image))

    {

        Bitmap newImage = new Bitmap(Image.FromStream(msToRead), new Size(size, size));

        using (MemoryStream msToWrite = new MemoryStream())

        {

            newImage.Save(msToWrite, ImageFormat.Jpeg);

            image = new Byte[msToWrite.Length];

            msToWrite.Seek(0, SeekOrigin.Begin);

            msToWrite.Read(image, 0, image.Length);

        }

    }

}

 

 

 

DDay.iCal - an iCalendar class library

 

יצירת קבצי ics (קבצים שמשמשים בין השאר לקביעת פגישות ב - outlook) בעזרת DDay.iCal.

 
את הספרייה ניתן להוריד מכאן
 
 
דוגמא לשימוש (נניח שאתם כותבים handler שנקרא handler1)
ה - handler מקבל כפרמטר את תאריך ההתחלה וכמה זמן (בשניות) זה ייקח
 

public void ProcessRequest(HttpContext context)

{

    iCalendar iCal = new iCalendar();

    Event evt = iCal.Create<Event>();

 

    var qs = context.Request.QueryString;

    var start = qs["s"];

    var end = int.Parse(qs["d"]);

 

    evt.Start = new iCalDateTime(DateTime.Parse(start));

    evt.End = new iCalDateTime(DateTime.Parse(start).Add(TimeSpan.FromSeconds(end)));

    evt.Description = "Body";

    evt.Location = "Sela Collage Channel";

    evt.Summary = "Subject";

 

 

    iCalendarSerializer serializer = new iCalendarSerializer();

    serializer.Serialize(iCal, context.Response.OutputStream, Encoding.UTF8);

 

    context.Response.AppendHeader("Content-Disposition", "attachment; filename=fileName.ics");

}

 
ובעזרת ה - iCal מייצרים את הפגישה. כשמשתמש ילחץ על לינק שכזה:
 

<a href="Handler1.ashx?s=2010/06/05 15:00:00&d=1500">ics</a>

 
הוא יוכל לשמור פגישה ביומן בתאריך 06/05/2010 שתמשך כ - 25 דקות

Quick guide to LINQ (Language Integrated Query)

המדריך המהיר ל - LINQ.

מטרת הפוסט:
בסיומו של פוסט זה אתם תדעו לכתוב קוד LINQ בשני התחבירים שלו ותבינו למה כדאי לעבוד עם LINQ ומה זה נותן לנו בתור מפתחים.
 
 
תוכן עניניים
  • מה זה לינק ומה נותן לנו השימוש בו.
  • לפני LINQ.
    • var keyword
    • Anonymous Types
    • Extension Methods
    • Lambda Expressions
  • כתיבת קוד שמדמה LINQ
  • תחביר LINQ
    • Extension Methods
    • Syntactic sugar
  • PLINQ

 

מה זה LINQ ומה נותן לנו השימוש בו.
הרעיון המרכזי מאחורי השימוש ב – LINQ הוא להפסיק לכתוב "איך" ולהתחיל לכתוב "מה", כלומר כשאנחנו כותבים קוד אנחנו רגילים לכתוב קוד שהמשמעות שלו הם הוראות למחשב איך לבצע את מה שאנחנו רוצים לעשות, למשל אם אנחנו רוצים לכתוב קוד שמוצא את כל המספרים הזוגיים במערך נכתוב מן הסתם קוד כזה:
 

IEnumerable<int> arr = Enumerable.Range(1, 100).ToArray();

List<int> list = new List<int>();

 

foreach (int item in arr)

{

    if (item % 2 == 0)

    {

        list.Add(item);

    }

}

למעשה כתבנו לולאה שרצה על כל המספרים ובודקת האם המספר מתחלק ב – 2, במידה וכן נוסיף אותו לרשימה החדשה – וכמו שאפשר לראות אנחנו מפרטים בדיוק צעד אחר צעד איך אנחנו רוצים לבצע את הקוד שלנו, לעומת זאת כשנכתוב LINQ נרצה לכתוב "מה" אנחנו רוצים לעשות ולא מעניין אותנו שלבי הביצוע ונכתוב קוד מהסוג הזה:

List<int> list = arr.Where(item => item % 2 == 0).ToList();

 
 
מאוחר יותר נבין את המשמעות של כל תג ופסיק מהמשפט, אבל גם בלי להבין את LINQ אפשר לראות שאיכשהו אנחנו מפעילים שאילתת חיתוך (Where) על המערך וכותבים שאנחנו רוצים רק את המספרים שמתחלקים בשניים item % 2 == 0.
לפני LINQ
var keyword
אפשר להשתמש ב – var במקום לכתוב את ה – type המלא, זה יכול לחסוך כתיבה לדוגמא:

var list = new List<string>();

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

var list = null;

או קוד כזה

static void f(var num)

 
מכיוון שהקומפיילר לא יודע באיזה טיפוס מדובר.
לכאורה זה נראה כמיותר אבל למעשה צריך את ה – var לשימוש ה - Anonymous Types כפי שנראה בהמשך ובנוסף כשאתם מגדירים טיפוס מורכב (כמו Dictionary עם כל מיני טיפוסים) ה – var יכול לפשט את קריאות הקוד.
Anonymous Types
יש לנו את היכולת לייצר בזמן כתיבת הקוד טיפוסים חדשים מבלי להגדיר אותם במפורש, לדוגמא:

var pi = new

{

    Name = "Shlomo",

    Age = 25

};

 

Console.WriteLine("Name: {0}, Age: {1}", pi.Name, pi.Age);

למעשה מאחורי הקלעים מוגדר בזמן קומפילציה מחלקה חדשה עם שני המאפיינים שהוגדרו (הם לקריאה בלבד).

למעשה הסיבה שהמציאו את var היא מכיוון שבלעדיה אי אפשר היה להגדיר ולהשתמש ב - Anonymous Types, כמובן שאפשר היה להשתמש ב – object (מה שאנחנו צריכים לעשות במידה ונרצה לשלוח את המשתנה למתודה אחרת) אבל במקרה כזה הגישה למאפיינים היא רק בעזרת reflection שזה לא יעיל כמובן.
 
בנוסף אפשר לכתוב קוד כזה:
 

class MyClass

{

    public int Age { get; set; }

    public string Name { get; set; }

}

 

class YourClass

{

    public float Salary { get; set; }

}

 

MyClass mc1 = new MyClass();

YourClass yc1 = new YourClass();

 

var pi1 = new

{

    mc1.Age,

    mc1.Name,

    yc1.Salary

};

 
כמו שאפשר לראות אפשר להגדיר את מאפייני הטיפוס האנונימי מבלי לתת להם שם במפורש והם יקבלו את השם מהמאפיין שהם לוקחים את הערך שלו.
לכאורה כל הסיפור הזה נראה מיותר – בהמשך כשנלמד LINQ נראה שלפעמים אין ברירה וחייבים להשתמש בתחביר הזה.
 
Extension Methods
נניח שיש מחלקה שלא אנחנו כתבנו (כמו string) ואנחנו רוצים להרחיב אותה (כלומר להוסיף לה פונקציות) נוכל כמובן לרשת ממנה (במקרה של string אי אפשר – זוהי דוגמא) אבל במקרה הזה בכל מקום באפליקציה שלנו נצטרך להשתמש בטיפוס שלנו וכמובן מה קורה אם באמת רוצים להוסיף ל – string שכאמור אי אפשר לרשת ממנה, בשביל זה יש את ה - Extension Methods, לדוגמא:
 

static class StringExtension

{

    public static int ToInt(this string s)

    {

        return int.Parse(s);

    }

}

וכעת נוכל לכתוב

int num = Console.ReadLine().ToInt();

 
מכיוון ש – ReadLine מחזיר אובייקט מסוג string וכל אובייקט מסוג string קיבל מתודה חדשה שנקראת ToInt.
זה מאוד נוח ונותן לנו את היכולת להוסיף פונקציונאליות לאובייקטים שלא אנחנו כתבנו ומשתמשים בה ברחבי המערכת.
 
כדי שפונקציה תתווסף לטיפוס מסוים צריך לעשות את הדברים הבאים:
  • להגדיר מחלקה סטטית.
  • להגדיר מתודה סטטית שהפרמטר הראשון שלה מוגדר עם this וכל המופעים של הטיפוס שלו (הפרמטר הראשון) יקבלו את המתודה החדשה.
  • ה – namespace שבו הוגדרה המתודה החדשה צריכה להיות מוגדרת בקובץ שבו רוצים להשתמש במתודה החדשה, כלומר אם המחלקה הסטטית תוגדר ב – namespace בשם a ובקובץ אחר באפליקציה לא יהיה using ל – a המתודה החדשה לא תופיע, כדי לגרום לכך שלא יצטרכו באמת להוסיף using שונים אפשר להגדיר namespace בשם MyExtension ותחתיו להגדיר את כל המחלקות החדשות, אופציה נוספת היא לתת את ה – namespace של המערכת למשל – אם מדובר בתוספות ל – string לעטוף את המחלקה ב – namespace של system וכן הלאה.

 

Lambda Expressions
בגרסאות הראשונות של השפה במידה והשתמשנו  ב – delegate היינו צריכים לכתוב מתודה מיוחדת לשליחה, לדוגמא
 

static void Print(int number)

{

    Console.WriteLine(number);

}

 

Action<int> action = new Action<int>(Print);

action(10);

 
(כמובן שלא היה קיים generic אבל לצורך המחשת נושא ה – delegate נניח שהיה קיים.)
במידה ורצינו להשתמש ב – delegate של Action היינו צריכים להגדיר מתודה שעומדת בחתימה ולשלוח אותה כפרמטר ל – ctor של ה – delegate.
 
בגרסה 2.0 של השפה קבלנו את המושג של anonymous methods ויכולנו לכתוב קוד כזה.
 

Action<int> action = delegate(int num1)

{

    Console.WriteLine(num1);

};

 

action(10);

 
הסיבה המרכזית לתמיכה ב - anonymous methods היא היכולת להשתמש במשתנים לוקליים של הפונקציה שקוראת ל – delegate לדוגמא:
 

int userNum = int.Parse(Console.ReadLine());

Action<int> action = delegate(int num1)

{

    if (userNum == 10)

        num1++;

 

    Console.WriteLine(num1);

};

 

action(10);

 
בגרסאות הקודמות של השפה שימוש במשתנה userNum היה מחייב להגדיר אותו כמשתנה גלובלי של המחלקה לעומת זאת החל מגרסה 2.0 ניתן להשתמש בכל המשתנים הלוקליים של הפונקציה שמפעילה את אותו delegate
החל מהגרסאות המתקדמות של השפה ניתן לכתוב Lambda Expressions שזהו תחביר אחר ל - anonymous methods (אחרי קומפילציה קיים רק anonymous methods) לדוגמא:
 

Action<int> action1 = num1 => Console.WriteLine(num1);

 

Action<int> action2 = num1 =>

{

    if (userNum == 10)

        num1++;

 

    Console.WriteLine(num1);

};

 
נפרק את השורה.
 
  1. Action<int> action1 =

  2. num1 =>

  3. Console.WriteLine(num1);

 
השורה הראשונה זה כמובן המופע של ה – delegate,
השורה השנייה מכילה את רשימת הפרמטרים, במידה ויש יותר מפרמטר אחד צריך לעטוף אותם בסוגריים, לאחר רשימת הפרמטרים יש הסימון המוזר של <=,
השורה השלישית מכילה את הקוד עצמו, במידה ויש רק שורה אחת לא צריך סוגריים מסולסלות ואם ה – delegate אמור להחזיר ערך לא צריך לכתוב את המילה return, למשל:
 

Func<int, int> mul = num1 => num1 * 10;

int res = mul(50);

 
הגדרנו מופע של delegate שאמור לקבל מספר ויכפיל אותן ב – 10 ולאחר מכן הפעלנו אותו, וכמו שאפשר לשים לב אין צורך לכתוב את המילה return.
 
 
כתיבת קוד שמדמה LINQ
נניח שאנחנו רוצים לכתוב מתודה חדשה שיודעת לפלטר List של int לפי תנאי שהיא מקבלת כפרמטר, נוכל לכתוב מתודה כזאת:
 

static List<int> Filter(List<int> source, Predicate<int> prediacte)

{

    var newList = new List<int>();

 

    foreach (var item in source)

    {

        if (prediacte(item))

            newList.Add(item);

    }

 

    return newList;

}

 
והשימוש בה יהיה כך:
 

List<int> list = Enumerable.Range(0, 50).ToList();

var evenList = Filter(list, x => x % 2 == 0);

 
כמו שאפשר לראות השתמשנו במתודה ושלחנו אליה את האובייקט המקורי ומימוש ל Lambda Expressions ל – Prediacte (אמורה לקבל פונקציה שתקבל T ותחזיר bool)
אם נרצה להיות יותר חכמים נגדיר את Filter כ - Extension Methods לכל List<int ובמקרה הזה נכתוב כך:

static List<int> Filter(this List<int> source, Predicate<int> prediacte)

{

    //....

}

 

var evenList = list.Filter(x => x % 2 == 0);

 
 
וזה בעצם מה שקרה – הוגדרו מספר רב של Extension Methods לאובייקט:

IEnumerable<T>

 
ולכן כל האוספים יכולים להשתמש במגוון רחב של מתודות חדשות. לדוגמא

if (list.Any(x => x == 10)) { }

כדי לבדוק האם יש ברשימה את הערך 10. דוגמא נוספת:

var evenIdsAsString = list.Where(x => x % 2 == 0)

                          .Select<int, string>(x => x.ToString());

 
מוציא מהרשימה את כל המספרים הזוגיים וממיר אותם למחרוזת, מה שבסופו של דבר יחזיר לי רשימה חדשה של מחרוזות עם מספרים זוגיים.
ובעזרת רשימת המתודות ניתן לכתוב קוד נקי ופשוט יותר.
 
 
תחביר LINQ
Extension Methods
את Extension Methods ראינו כבר בחלק הקודם, נראה עוד דוגמה אחת יחסית מורכבת, נניח שאנחנו רוצים לחפש את כל התיקיות שיש להם מעל 10 קבצים ולפחות קובץ אחד גדול יותר מ – 20 KB, בנוסף נרמה לייצר מחלקה משלנו ולא להחזיר אובייקט של DirectoryInfo.
 
נגדיר מחלקה שנראת כך:

class MyDirectoryInfo

{

    public int FileNumbers { get; set; }

    public string DirectoryName { get; set; }

}

 

var x1 = Directory.GetDirectories("path", "", SearchOption.AllDirectories)

            .Select(x => new DirectoryInfo(x))

            .Where(x =>

                {

                    var files = x.GetFiles();

                    return files.Length >= 10 && files.Any(fi => fi.Length > 10);

                })

            .Select(x => new MyDirectoryInfo()

            {

                DirectoryName = x.Name,

                FileNumbers = x.GetFiles().Length

            });

 
במידה ולא היה לנו מחלקה שנקראת MyDirectoryInfo יכולנו להחזיר Anonymous Types.
 
 Syntactic sugar
כדי להקל על הכתיבה (לפעמים) אפשר לכתוב (חלק) משאילתות LINQ בתחביר אחר. נתחיל עם משהו פשוט יחסית, מציאת כל העובדים שהמשכורת שלהם קטנה מ – 2000
 

class Employee

{

    public string Name { get; set; }

    public double Salary { get; set; }

}

 
בתחביר הקודם היינו כותבים קוד כזה:
 

List<Employee> listEmp = GetList();

var lowSalary = listEmp.Where(x => x.Salary < 2000);

 
בתחביר החדש ניתן לכתוב כך

var lowSalary = from n in listEmp

                where n.Salary < 2000

                select n;

 
את השאילתא המורכבת (של הקבצים) ניתן לתרגם לזה
 

var directories = from n in Directory.GetDirectories("path", "", SearchOption.AllDirectories)

                  let di = new DirectoryInfo(n)

                  let files = di.GetFiles()

                  where files.Length >= 10 && files.Any(x => x.Length > 10)

                  select new MyDirectoryInfo()

                  {

                      DirectoryName = di.Name,

                      FileNumbers = files.Length

                  };

 
 
 
PLINQ
PLINQ הוא parallel linq (רק מגרסה 4.0 ומעלה) מאפשר לנו בקלות לבצע את שאילתות ה - LINQ בצורה פרללית (כלומר כמה threads שיבצעו את העבודה) אני לא ארחיב בנושא מכיוון שזה נושא בפני עצמו, אני רק אדגים דוגמא קטנה
 

List<int> list = Enumerable.Range(1, 50000000).ToList();

 

for (int i = 0; i < 4; i++)

{

    Stopwatch sw = Stopwatch.StartNew();

    var count = list.Count(x => IsPrime(x));

    sw.Stop();

    Console.WriteLine(sw.ElapsedMilliseconds);

 

    sw = Stopwatch.StartNew();

    count = list.AsParallel().Count(x => IsPrime(x));

    sw.Stop();

    Console.WriteLine(sw.ElapsedMilliseconds);

 

    Console.WriteLine("===========");

}

 

static bool IsPrime(int num)

{

    for (int i = num - 1; i > 1; i++)

    {

        if (num % i == 0)

            return false;

    }

 

    return true;

}

 
בדוגמא הזאת אנחנו מפעילים על כל איבר את הפונקצייה IsPrime (שמוממשת בצורה שייקח לה זמן להתבצע) ואנחנו סוכמים כמה מספרים ראשוניים יש במערך.
 
הדוגמא הראשונה משתמשת ב - LINQ רגיל לעומת הדוגנא השנייה שמתשמש ב - PLINQ בעזרת קריאה ל - AsParallel.
 
הקוד הפרללי רץ במיהירות כפולה (אצלי במחשב) מהקוד הרגיל.
 
 
סיכום
LINQ הוא כלי מדהים שבעזרתו אפשר לשנות את הרגלי הכתיבה שלנו ולכתוב קוד קריא יותר ומהיר יותר גם מבחינת הביצועים וגם מבחינת מהירות הכתיבה.

ajax control toolkit combobox - style, add item from javascript, clear items from javascript, get selected value

 

בפוסט זה נראה איך אפשר לעבוד עם Ajax:ComboBox

 
 
(קרדיט ליוסי גולדברג על הפוסט הזה)
 
 
מי שעדיין לא מכיר את הפקד מוזמן לקרוא עליו בלינק למעלה, בפוסט הזה נדבר על הדברים הבאים:
 
איך מעצבים אותו ב - Windows Style.
 
איך מוסיפים מ - Javascript עוד ערכים.
 
איך מנקים אותו מערכים.
 
איך מקבלים ב - javascript את מה שהמשתמש בחר.
 
איך נרשמים ל - onchange ב - javascript.
 
 
 
השימוש הראושני בו הוא פשוט.
 
השלבים הראשונים משותפים לכל פקדי ה - ajax toolkit
 
 
1. הוספת reference ל - dll (אפשר להוריד מכאן)
 
2. הוספת השורות הבאה בקונפיג
 

<pages>

  <controls>

    <add assembly="AjaxControlToolkit" namespace="AjaxControlToolkit" tagPrefix="ajax"/>

  </controls>

</pages>

3. הוספת script manager של ה - toolkit
 

<ajax:ToolkitScriptManager ID="sm1" runat="server">

</ajax:ToolkitScriptManager>

 
4. הגדרת ה - combo
 

<ajax:ComboBox ID="ComboBox1"

        runat="server"

        AutoPostBack="False"

        DropDownStyle="DropDownList"

        AutoCompleteMode="SuggestAppend"

        CaseSensitive="False"

        ItemInsertLocation="Append"

        DataTextField="Name"

        DataValueField="Id">

</ajax:ComboBox>

 
אני לא אעבור כרגע על המאפיינים השונים (תוכלו לקרוא באתר של ajax toolkit).
 
נתחיל עם העיצוב - כשתקראו על הפקד באתר שלהם תראו שאת המאפיין CssClass אפשר להגדיר ל - WindowsStyle (למשל) כדי שזה יראה כמו הפקדים של windows, מה שהם לא מספרים שם שזה ה - style שצריך להוסיף. (התמונה מצורפת בדוגמת הקוד להורדה)
 

<style type="text/css">

.WindowsStyle .ajax__combobox_inputcontainer .ajax__combobox_textboxcontainer INPUT

{

    border-bottom: #7f9db9 1px solid;

    border-left: #7f9db9 1px solid;

    padding-bottom: 0px;

    margin: 0px;

    padding-left: 5px;

    padding-right: 0px;

    height: 18px;

    font-size: 13px;

    border-top: #7f9db9 1px solid;

    border-right: 0px;

    padding-top: 1px;

}

.WindowsStyle .ajax__combobox_inputcontainer .ajax__combobox_buttoncontainer BUTTON

{

    background-image: url(windows-arrow.gif);

    border-bottom: 0px;

    border-left: 0px;

    padding-bottom: 0px;

    margin: 0px;

    padding-left: 0px;

    width: 21px;

    padding-right: 0px;

    background-position: left top;

    height: 21px;

    border-top: 0px;

    border-right: 0px;

    padding-top: 0px;

}

.WindowsStyle .ajax__combobox_itemlist

{

    border-bottom-color: #7f9db9;

    border-top-color: #7f9db9;

    border-right-color: #7f9db9;

    border-left-color: #7f9db9;

    font-family: Arial;

    font-size: 12px;

}

</style>

 
 
נניח שהקוד בצד השרת נראה כך:
 

DataTable table = new DataTable();

table.Columns.Add("Id");

table.Columns.Add("Name");

 

table.Rows.Add("1", "Shlomo");

table.Rows.Add("2", "Yossi");

table.Rows.Add("3", "Noam");

table.Rows.Add("4", "Ron");

table.Rows.Add("5", "Tomer");

table.Rows.Add("6", "Shlomo");

table.Rows.Add("7", "Dudu");

table.Rows.Add("8", "Nama");

table.Rows.Add("9", "Roni");

table.Rows.Add("10", "Sulamit");

 

ComboBox1.DataSource = table;

ComboBox1.DataBind();

 
 
כעת אנו רוצים בצד הלקוח להרשם ל - oncahnge ולדעת מה ה - value שהמשתמש בחר -
שני בעיות - הראשונה יש להם כנראה באג וה - value לא נשלח ללקוח, שנייה הם לא ממשו onchange בצד הלקוח.
 
הפיתרון לבעייה הראשונה זה להוסיף את הקוד הבא (לאחר ה - DataBind)
 

foreach (ListItem item in ComboBox1.Items)

{

    item.Attributes["value"] = item.Value;

}

 
הפיתרון לבעייה השנייה זה להירשם לכל האירועים של הפקד (בצד הלקוח) ולבדוק האם מדובר ב - selectedChange, כדי לעשות את זה נצטרך אחרי טעינת העמוד להפעיל את הפונקצייה הבאה
 

function AddOnChangeEvent(id) {

    $find(id).add_propertyChanged(function(sender, e) {

        if (e.get_propertyName() == 'selectedIndex') {

            var selectedIndex = sender.get_selectedIndex();

            var newValue = $get(id).getElementsByTagName('LI')[selectedIndex].value;

 

            alert(newValue);

        }

    })

}

 
הפונקצייה מקבלת את ה - id ובכל פעם שמאפיין מסויים ישתנה בפקד וזה יהיה selectedIndex נגיע לפונקצייה שלנו ונוכל להוציא את ה - value שהמשתמש בחר (כזכור צריך להוסיף את ה - value ידנית לאחר קריאה ל - DataBind)
 
לכאורה צריך לקרוא למתודה הזאת בזמן onload של העמוד - אבל מסתבר שקריאה ל - find תחזיר null ב - 10 מילי-שניות הראשונות של טעינת העמוד מכיוון שהוא עדיין לא סיים לבנות את האובייקט, יכול להיות ש - jQuery יכול לעזור כאן, אבל אם אתם לא רוצים תוכלו לכתוב את הקוד הבא: (אחרי ההגדרה של ה - ScriptManager)
 

<script type="text/javascript">

    Sys.UI.DomEvent.addHandler(window, "load", function() {

        setTimeout("AddOnChangeEvent('<%=ComboBox1.ClientID %>')", 10);

    });

</script>

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

function DeleteAllComboBoxItems(DdlId) {

    var box = $find(DdlId);

    var items = box._optionListItems;

    items.splice(0, items.length);

    var listctl = box.get_optionListControl();

    while (listctl.childNodes.length > 0) {

        listctl.removeChild(listctl.childNodes[0]);

    }

    var textbox = box.get_textBoxControl();

    textbox.value = "";

}

 
כדי להוסיף ערכים חדשים תוכלו להפעיל את הפונקצייה הבאה:
 

function AddNewItemToCombo(combo, text, value) {

    // delete the old height so the new height will calculate with the new item

    combo._optionListHeight = null;

 

    // build the new Item with a test value  

    var newItem = document.createElement('LI');

    newItem.innerHTML = text;

    newItem.amdValue = value;

    newItem._textIsEmpty = "false";

 

    // add this Item

    combo._optionListControl.appendChild(newItem);

 

    // we need to update the ComboBox' popupBehavior with the new OptionList  

    // dispose of popup extender

    if (combo._popupBehavior) {

        combo._popupBehavior.remove_showing(combo._popupShowingHandler);

        combo._popupBehavior.remove_shown(combo._popupShownHandler);

        combo._popupBehavior.remove_hiding(combo._popupHidingHandler);

        combo._popupBehavior.dispose();

        combo._popupBehavior = null;

    }

 

    // clear old Handlers and Delegates

    combo.clearHandlers();

    combo.clearDelegates();

 

    // initialize New OptionList, create new Delegates and Handlers

    combo.createDelegates();

    combo.initializeOptionList();

    combo.addHandlers();

}

Add New Item dialog is empty

 

כיצד לשחזר את האלמנטים בחלון ה - Add New Item

 
 
לאחרונה קרתה לי תקלה מוזרה בכל פרוייקט שפתחתי הפעלה של Add New Item פתחה חלון ריק, לא ברור לי למה זה קרה אבל ה - event viewer המליץ לי להפעיל את
 
devnet /installvstemplates וזה אכן שיחזר את כל האלמנטים בחלון.