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

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

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

the installed product does not match the installation source

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

the installed product does not match the installation source(s), until a matching source is provided or the installed product and the source are synchronized, this action cannot be performed
 
 
 
זה יכול להיות לפעמים אם עוצרים את תוכנית התקנה בעזרת ה – Task Manager כי היא נתקעת וכד'.
 
ראיתי פתרון נחמד כאן.
 
MsiExec.exe /I "Install.msi" REINSTALLMODE=voums REINSTALL=ALL
 
 
כשכמובן יש לכם את ה – MSI המקורי, אבל אתם לא מצליחים להסיר אותה מ – Add or remove programs, שורת הפקודה תדע להסיר אותה בכל זאת.

שליחת מקשי מקלדת לחלונות אחרים מתוך תהליכים

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

var p1 = Process.GetProcessById(2516);

var p1 = Process.GetProcessesByName("Calcaulator")[0];

 
 
במידה ואתם רוצים למצוא את החלון לפי הכותרת שלו, תוכלו לכתוב את הקוד הבא:
 

[DllImport("user32.dll")]

private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

 
 
וכעת בקוד:
 

var myWindowHandle = FindWindow(null, "[Window Title]");

 
 
ההבדל, שבמידה ונשתמש עם Process נצרך לאחר מכן לגשת למאפיין MainWindowHandle, והמתודה FindWindow מחזירה כבר את ה – Handle.
 
 
כעת נכתוב את הקוד הבא:
 

SetForegroundWindow(p1.MainWindowHandle);

SendKeys.SendWait("10");

SendKeys.Flush();

 
 
צריך להוסיף System.Windows.Forms.dll (במידה ואתם לא נמצאים באפליקציית Win Forms), צריך לדאוג שהחלון החלו יהיה החלון שכרגע בפוקוס – כדי שיהיה לנו את המתודה המתאימה נוסיף את הקוד הבא:
 
 

[DllImport("user32.dll")]

private static extern bool SetForegroundWindow(IntPtr hWnd);

 
 
וכעת ניתן לשלוח כל מקש שנרצה לחלון המתאים.
 
נקודה קטנה – במידה ואתם בסביבת Win Application, ניתן לקרוא למתודת Send במקום מתודת SendWait.

מדריך מקוצר ל - ClickOnce חלק 3 מתוך 3

בפוסט הראשון ראינו בקצרה כיצד ניתן לייצר Click Once Deployment, בפוסט השני ראינו כיצד להשתמש עם ה – API שלהם בכדי לייצר התקנה שתייבא את ה – Click Once Application (אמנם ראינו את הקוד ב – Console Application – אבל הרעיון היה ברור).
 
כפי שהבטחתי, הפעם נראה כיצד נגרום לעדכונים אוטומטיים עבור האפליקציה שלנו.
 
דוגמת הקוד נלקחה מה – MSDN.
 
זה יקרה למעשה מתוך האפליקציה עצמה. (כלומר אותה אפליקציה שהותקנה בעזרת Click Once)
 
נוכל כמובן להפעיל את הקוד בעזרת פעולה יזומה של המשתמש (Check for Update), או שנוכל לבדוק כל פעם כשהאפליקציה נפתחת או בכל זמן אחר.
 
ראשית נצטרך לקבל את המופע של המחלקה ApplicationDeployment בצורה הבאה:
 

ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;

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

ad.CheckForUpdateCompleted += ad_CheckForUpdateCompleted;

ad.CheckForUpdateProgressChanged += ad_CheckForUpdateProgressChanged;

 

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

ad.CheckForUpdateAsync();

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

void ad_CheckForUpdateProgressChanged(object sender, DeploymentProgressChangedEventArgs e)

{

 

}

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

void ad_CheckForUpdateCompleted(object sender, CheckForUpdateCompletedEventArgs e)

{

 

}

 
 
 
 
גם כאן המשתנה e יכיל מידע בעל ערך, כמו למשל e.Error שיגיד לנו האם היה שגיאה e.Cancelled, שיגיד לנו האם הבקשה התבטלה, המידע החשוב הוא e.UpdateAvailable שיודיע לנו האם יש עדכון, ו – e.IsUpdateRequired שיעדכן אותנו האם עדכון זה הוא חובה.
 
בסופו של תהליך נכתוב את הקוד הבא: (אם נעבוד בצורה אסינכרונית)
 

ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;

 

ad.UpdateCompleted += ad_UpdateCompleted;

ad.UpdateProgressChanged += ad_UpdateProgressChanged;

 

ad.UpdateAsync();  

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

מדריך מקוצר ל - ClickOnce חלק 2 מתוך 3

בהמשך לפוסט הקודם, שדיבר על יצירת התקנה מסוג Click Once, נראה הפעם כיצד להשתמש ב - API כדי לממש מנגנון כזה לבד - כלומר להשתמש במנגנון המובנה של net ולייצר UI משלנו למנגנון.
 
 
הקוד שאכתוב כאן והדוגמאות יהיו ב - Console Application, אבל כמובן שבדרך כלל כשנרצה מנגנון משלנו ל - Click Once יהיה (בעיקר) כדי לממש UI לבד.
 
 
למעשה ה - API מחולק לשניים, הפעם הראשונה בה אנחנו מתקינים את התוכנה, והפעמים האחרות בהם אנחנו בודקים עידכונים, בפוסט זה נדבר על הפעם הראשונה, ובפוסט הבא נדבר על העידכונים.
 
הדוגמאות קוד מגיעות מה - MSDN (שם יש דוגמת קוד מלאה - עם טיפול בשגיאות וכדו' - פוסט מומלץ ביותר)
 
 
לאחר שנעשה publish, לאפליקציה שלנו (כפי שראינו בפוסט הקודם), נשתמש ב - API כדי לגרום להתקנה שלו.
 
נוכל לייצר אפלייקציה בה יהיה הקוד הבא:
 

static void Main(string[] args)

{

    string deployFile = @"file://[FOLDER]\WindowsFormsApplication2.application";

    Uri deploymentUri = new Uri(deployFile);

    var iphm = new InPlaceHostingManager(deploymentUri, false);

    iphm.GetManifestCompleted += iphm_GetManifestCompleted;

    iphm.GetManifestAsync();

    Console.ReadLine();

}

 
ראשית, נגדיר את ה - URI לקובץ עם סיומת ה - application. (שנוצר כתובצאה מה - publish שעשינו)
 
לאחר מכן נייצר מופע של המחלקה שיודעת להתקין ClickOnce, ונשלח לה כפרמטר את ה - URI וערך שאומר שמדובר גם באפליקציה שמוגדרת לעבוד כ - offline.
 
היות שכל העבודה עם המחלקה הזאת היא אסינכרונית, ראשית נרשם לאירוע של סיום הורדת הקובץ, ורק לאחר מכן נבקש את הקובץ (כמובן שנקרא ל - ReadLine כך שהתוכנית לא תסגר לפני שהכול יסתיים)
 
 
לאחר שקבלנו את הקובץ, נכתוב את הקוד הבא:
 

static void iphm_GetManifestCompleted(object sender, GetManifestCompletedEventArgs e)

{

    // Check for an error.

    if (e.Error != null)

    {

        // do ...

    }

    var iphm = (InPlaceHostingManager)sender;

    iphm.AssertApplicationRequirements(true);

    iphm.DownloadProgressChanged += iphm_DownloadProgressChanged;

    iphm.DownloadApplicationCompleted += iphm_DownloadApplicationCompleted;

    iphm.DownloadApplicationAsync();

}

המשתנה e מכיל הרבה מידע אודות ההתקנה (בדוגמא ב - MSDN, משתמשים במידע הזה הרבה (וכנראה גם בחיים האמיתיים))
 
חייבים להפעיל את פונקציית AssertApplicationRequirements כדי לוודא את דרישות הקדם (הערך true מוודא גם בדיקות הרשאות)
 
נרשם לאירועים של התקדמות ההורדה, וסיום ההורדה - ונתקין את התוכנה.
 
למעשה זהו פחות או יותר, כל מה שנשאר לנו זה לממש UI יפה וחמוד (אחרת ניתן פשוט להשתמש בזה של מייקרוסופט)
 
 
בפוסט הבא נראה כיצד נשתמש ב - API שלהם כדי לקבל עידכוני תוכנה.

מדריך מקוצר ל - ClickOnce חלק 1 מתוך 2

 

סיימנו (או התחלנו לסיים) את פיתוח האפליקציה, ואנחנו רוצים לייצר תוכנית התקנה, בעולם ה - net קיימת בפנינו שני אפשרויות עיקריות, הראשונה היא לייצר msi, שזה נושא בפני עצמו, והשנייה היא להגדיר את האפליקציה שלנו כ - ClickOnce Appliation
 
יש הבדלים שונים בין התקנה בעזרת msi לבין התקנה בעזרת ClickOnce, ההבדל המרכזי ביניהם, היא שהתקנה בעזרת ClickOnce היא יותר פשוטה (עבור המשתמש ועבור המפתח) ויש לה מנגנון עידכונים אוטמטיים כשמחליטים להעלות גרסה חדשה.
 
 
 
 
בפוסט זה נראה כיצד מגדירים ClickOnce בכמה צעדים פשוטים.
 
 
לאחר שסיימנו לפתח את האפליקציה, נלך למאפייני הפרוייקט ונבחר בטאב Publish
 
 
Publish 1
 
 
נגדיר את התיקייה לאן הקבצים יפובלשו. (אין צורך להגדיר את ה - Installation Folder Url)
 
נבחר את Mode ההתקנה (בדרך כלל נעדיף לבחור באופצייה השנייה, בה ניתן להריץ את התוכנה גם במצב offline)
 
נגדיר את גרסת האפליקציה.
 
תחת Appliation Files נוכל לבחור אילו קבצים מתוך הפרוייקט יהיו מפובלשים ואילו לא.
 
תחת Prerequisite נוכל לבחור את דרישות הקדם של האפליקציה (כמו למשל גרסת net וכד')
 
תחת Updates נוכל להגדיר האם יהיו automatic update או לא (ברירת המחדל, לא) ואם כן כל כמה ימים או בכל הפעלה - ועוד הגדרות הקשורות לנושא.
 
תחת Options נוכל להגדיר הגדרות כמו שפה, שם החברה, לינק לאתר שלכם וכד', בנוסף נוכל להגדיר האם לייצר קיצורי דרך על שולחן העבודה ועוד הגדרות שונות.
 
 
למעשה זהו פחות או יותר, לחיצה על Publish Now תייצר תייקיה ובה תיקייה בשם Application Files עם כל הקבצים הנדרשים, קובץ setup וקובץ עם סיומת application המכיל את ההגדרות של Click Once.
 
כעת ניתן להעלות את הקבצים הללו לשרת web כלשהו, כל אחד יוכל להתקין את האפליקציה, במידה והגדרתם auto update, והעליתם גרסה חדשה, האפליקציה תעודכן אוטומטית.
 
 
בפוסט הבא נכיר את ה - api של CliakOnce ונראה כיצד לייצר UI משלנו תוך שימוש במנגנון של ClickOnce.

Text Resource in asp.net mvc

 

 פוסט זה נכתב בעזרתם של תותחי העל נטלי אהפוטה וניב לוי (יהלום של סלע)
 
 
כרגיל בהרבה מקומות בהם אנחנו מפתחים אתרי אינטרנט, רוצים המנהלים שליטה על המחרוזות בלי צורך להזדקק למפתחים, הדרך הרגילה לעבוד עם מחרוזות היא בעזרת קבצי resx, שאיתם כמובן יש בעייה מבחינת העבודה איתם (למי שלא מפתח) - בעבר כתבתי כלי שנותן מענה מסויים, אך בפעם הזאת החליט מי שהחליט שהמחרוזות ישמרו בבסיס נתונים, מה שמביא אותנו לנקודות הבאות:
 
  • המידע בבסיס הנתונים, ויש צורך להגדיר דף בו המנהלים יוכלו לערוך את המחרוזות.
  • שמירת המידע במקום כלשהו בזיכרון, כדי לא לגשת כל הזמן לבסיס הנתונים.
  • אפשרות לגשת למידע מקבצי ה - cshtml.
  • אפשרות לגשת למידע מקבצי java script.

 

המודל נראה כך:
 
Resource Model
 
  • Name - ה - key שדרכו מחפשים את הערך.
  • Comment - טקסט שמסביר למנהלים מה המשמעות של הערך.
  • Description - הערך עצמו.
  • IsUseInJs - האם הערך הזה צריך שתהיה לו גישה גם מצד הלקוח.
כעת הגדרנו משתנה סטטי המחזיר מתוך ה - Application את המידע, במידה והמידע לא קיים טוענים אותו.
 

public static List<Resource> Resources

{

    get

    {

        var list = HttpContext.Current.Application["Resources"];

        if (list == null)

        {

            UserModleEntities context = new UserModleEntities();

            //Detach the context to the object underline - read only with entity frame work;

            context.Resources.MergeOption = MergeOption.NoTracking;

            list = context.Resources.ToList();

            HttpContext.Current.Application["Resources"] = list;

        }

        return list as List<Resource>;

    }

}

 
 
כעת נרצה לאפשר בקלות את הגישה מקבצי ה - cshtml, ולכן כתבנו את הקוד הבא:
 

namespace System.Web.Mvc.Html

{

    public static class HtmlHelpersExtensions

    {

        public static string TextResource(this HtmlHelper helper, string name)

        {

            return ApplicationHelper.Resources.Where(x => x.Name == name).Select(x => x.Description).FirstOrDefault();

        }

    }

}

 
כעת בכל מקום בדף נוכל לכתוב:
 

@Html.TextResource("Welcome")

 
וזה יחזיר את הערך הנכון.
 
 
כפי שהגדרנו, אנחנ רוצים גם גישה מקבצי JS, בעבר פרסמתי משהו דומה עם קבצי resx.
 
נייצר קובץ ashx, ונכתוב את הקוד הבא:
 

public void ProcessRequest(HttpContext context)

{

 

    //Setting cache options

    //TimeSpan -Default cache evrey 60 seconds

    TimeSpan freshness = Properties.Settings.Default.ResourceCacheDif;           

    DateTime now = DateTime.Now;

    context.Response.Cache.SetExpires(now.Add(freshness));

    context.Response.Cache.SetMaxAge(freshness);

    context.Response.Cache.SetCacheability(HttpCacheability.Server);

    context.Response.Cache.SetValidUntilExpires(true);

 

 

    context.Response.ContentType = "application/js";

    //The list of all js resources

    List<Resource> jsListResources = ApplicationHelper.JsResources;

 

    //Constructing the array

    StringBuilder stringBuilder = new StringBuilder();

    stringBuilder.Append("var resourceArray = new Array(); ");

 

    foreach (Resource item in jsListResources)

    {

        stringBuilder.Append(string.Format("resourceArray['{0}'] = '{1}'; ", item.Name, item.Description));

 

    }

 

    context.Response.Write(stringBuilder.ToString());

 

}

 
כשהמאפיין JSResource מחזיר מערך של Resources שהוגדר עבורם UseInJs=true.
 
כעת ב - JS נוכל לכתוב בכל מקום
 

resourceArray.CaratsMandatory

Dynamically loading or removing an external JavaScript or CSS file

 

המקור לקוד בפוסט זה מגיע מכאן
 
לא מזמן כתבתי אתר קטן, שהיה צריך לתמוך במעבר בין עברית לאנגלית, כמובן שזה תמיד כאב ראש ויש כל מיני שיטות לעשות זאת (בעיקר הבעייה עם שינוי הכיוון)
 
 
בפעם הזאת בחרתי להתמודד עם הבעייה בצורה הבאה:
 
כתבתי קובץ css ובו העיצוב משמאל לימין כמו שצריך להיות באנגלית.
 
כתבתי קובץ css נוסף שבו מופיעים כל השנויים מבחינת העיצוב עבור השפה העברית (align, padding, marging, background וכד')
 
 
כעת במעבר לעברית הפעלתי את הפונקציה הבאה:
 

function loadjscssfile(filename, filetype) {

    if (filetype == "js") { //if filename is a external JavaScript file

        var fileref = document.createElement('script')

        fileref.setAttribute("type", "text/javascript")

        fileref.setAttribute("src", filename)

    }

    else if (filetype == "css") { //if filename is an external CSS file

        var fileref = document.createElement("link")

        fileref.setAttribute("rel", "stylesheet")

        fileref.setAttribute("type", "text/css")

        fileref.setAttribute("href", filename)

    }

    if (typeof fileref != "undefined")

        document.getElementsByTagName("head")[0].appendChild(fileref)

}

 
 
ושלחתי את שם קובץ ה - css של העברית, והיות שקובץ הזה ירד אחרון הוא דרס את כל ההגדרות של קובץ האנגלית.
 
במעבר לאנגלית הפעלתי את הקוד הבא:
 

function removejscssfile(filename, filetype) {

    //determine element type to create nodelist from

    var targetelement = (filetype == "js") ? "script" : (filetype == "css") ? "link" : "none"

 

    //determine corresponding attribute to test for

    var targetattr = (filetype == "js") ? "src" : (filetype == "css") ? "href" : "none"

 

    var allsuspects = document.getElementsByTagName(targetelement)

 

    //search backwards within nodelist for matching elements to remove

    for (var i = allsuspects.length; i >= 0; i--) {

        if (allsuspects[i] &&

            allsuspects[i].getAttribute(targetattr) != null &&

            allsuspects[i].getAttribute(targetattr).indexOf(filename) != -1) {

 

            //remove element by calling parentNode.removeChild()

            allsuspects[i].parentNode.removeChild(allsuspects[i])

        }

    }

}

 
כעת חזרו ההגדרות לפי מה שהוגדר באנגלית.
Posted: May 07 2012, 01:49 PM by Shlomo | with no comments
תגים:, , , ,

jquery ajax with async set to false and beforeSend registration

 

לאחרונה הגיעה אלי שאלה מעניינת,
 
אני משתמשת הרבה בקריאות סינכרוניות מקליינט לWCF.
לפני כל קריאה אני משנה את הcursor ל wait.
הבעיה היא שהוא כל כך מהיר שעוד לפני שהוא מספיק להפוך את העכבר – הוא כבר מגיע אל ה WCF ומקפיא את המסך, באופן שהעכבר נשאר כשהיה , עד שהקריאה חוזרת חזרה
(בקריאות א-סינכרוניות כמובן שזה לא קורה)
פתרתי את הבעייה הזו באמצעות
 window.setTimeout(function () { CallWCF(); }, 10);
ואז ה wait cursor מספיק להתבצע לפני שהקריאה נשלחת עקב ההשהייה.
 
האם יש פתרון מוצלח יותר במקום למלא את הקוד ב setTimeout (יש לי  קריאות רבות ל WCF)?
 
הקוד נראה כך (בערך): 
 

function click_click() {

 

 

    $.ajax({

        type: "POST",

        url: "/WebService1.asmx/HelloWorld",

        contentType: "application/json; charset=utf-8",

        dataType: "json",

        async: false,

        beforeSend: function () {

            setWaitCursor('wait');

        },

        success: function (res) {

            setWaitCursor('default');

        },

        error: function (e) {

 

        }

    });

}

 

function setWaitCursor(val) {

    $('*').css('cursor', val);

}

 

 
כעת הבעייה שנשאלת כיצד לגרום למסך לקבל את סימן ה - wait כשמפעילים את השאילתא ב - async:false.
 
בהתחלה, אמרתי שהדבר לא ייתכן - כפי שמופיע בדוקמנטצייה של jQuery (ובכל מקום אחר בגוגל) שבמידה ומדובר בקריאה סינכרונית שום דבר ב - UI לא יכול להשתנות לפני שחוזרים מהשרת - למעשה במידה וניתן היה לקרוא לקוד שהוגדר ב - beforeSend לפני הקריאה ל - $.ajax, היינו מסתפקים עם זה, הבעייה שגם הקוד שהופעל לפני הקריאה לשרת לא באמת התבצע אם היה מדובר בעבודה על ה - UI.
 
 
אבל לאחר חשיבה מאומצת הגעתי למסקנה, שאפשר לעבוד על הדפדפן. בשלבים הבאים:
 
 
ראשית נפעיל קוד מסויים בזמן כל קריאת ajax.
נבדוק האם המפתח מעוניין להריץ קוד בזמן beforeSend.
במידה וכן נבדוק האם הקריאה הוגדרה כסינכרונית.
במידה וכן, נבטל את הקריאה לשרת.
נריץ את ה - beforeSend.
ונריץ מחדש את הקריאה לשרת ללא ה - beforeSend.
 
 
כדי לגרום להריץ קוד מסויים עבור כל קריאת jquery ajax, נשתמש ב - ajaxPrefilter, נכתוב את בלוק הקוד הבא היכן שהוא בדף.
 

$.ajaxPrefilter(function (options, originalOptions, jqXHR) {

 

    // user are register to beforeSend event and define the async property to false

    if (originalOptions.beforeSend && !originalOptions.async) {

 

        // abort the xml http request object

        jqXHR.abort();

 

        // invoke the beforeSend registerd method

        originalOptions.beforeSend();

 

        // reinvoke the ajax call after 10 milisecound (enough time for ui thread)

        setTimeout(function () {

            // remove the beforeSend event

            originalOptions.beforeSend = null;

 

            // call to server

            $.ajax(originalOptions)

        }, 10);

    }

});

 
 
כעת כל קריאה ל - ajax (בעזרת jquery) תעבור דרך קטע הקוד שצויין, במידה ונרשמו ל - beforeSend וגם הגדירו שהפעולה תהיה סינכרונית, ראשית יבוצע ה - beforeSend, ורק לאחר כ - 10 מילישניות תקרא הפונקצייה מחדש, כשכמובן הקריאה ל - beforeSend נמחקת (אחרת תהיה רקורסייה אינסופית)

חלוקת האתר לכמה חלקים וכיצד לעדכן את השרת מהיכן הגיעה הקריאה אליו

לא מזמן התבקשתי לעזור לתכנן אפליקציית אינטרנט בה המסך יכול להיות מחולק לכמה חלקים, והמשתמש יכול לגלוש באותה אפליקצייה מכל חלק - כך שהוא יוכל לראות כמה חלקים שונים של אותה אפליקצייה, באותו מסך.
 
במידה שהיינו מתחילים לכתוב את האפליקצייה מאפס, כנראה שהיינו בוחרים ב - Single Applcation Page וכל האתר היה עובד ב - ajax, וכך לא היה שום בעיה לחלק את המסך לשניים או יותר חלקים.
 
הבעייה שהאתר כבר היה כתוב :-)
 
במקרה הזה לאחר חשיבה הגענו למסקנה שהשיטה הכי יעילה, היא לחלק את העמוד הראשי לכמה iframes שהמשתמש יוכל לנווט בכל חלק מבלי לגרום ל - post back לכל החלקים האחרים.
 
כאן הגענו לבעייה חדשה, היות שמדובר בטאב אחד - כל ה - iframes חולקים את אותו session, ואין שום דרך בצד השרת לדעת מאיזה חלק הגיע ה - post back, והיות שהרבה מידע עבור המשתמש היה נשמר ב - session - כמו בחירות של תפריטים וכדו', נניח שהמשתמש היה בוחר בחירה מסויימת באחד החלקים, והוא היה הולך לחלק שני ועושה משם post back, השרת היה מתייחס לבחירה של המשתמש מהחלק האחר.
 
לאחר חצי יום נסיונות כתבתנו את הקוד הבא:
 

<table>

    <tr>

        <td><iframe src="WebForm2.aspx" data-side="LeftTop"></iframe></td>

        <td><iframe src="WebForm2.aspx" data-side="RightTop"></iframe></td>

    </tr>

    <tr>

        <td><iframe src="WebForm2.aspx" data-side="LeftButtom"></iframe></td>

        <td><iframe src="WebForm2.aspx" data-side="RightButtom"></iframe></td>

    </tr>

</table>

 
 
לכל iframe הוספנו attribute בשם data-side עם ערך שמכיל היכן פיזית הוא יושב בדף (data הינו תוספת של html5 כך שמאפיינים שאינם בתקן יוכלו להתווסף לאלמנטים)
 
כעת כל iframe מכיל את המידע היכן הוא יושב, כעת נשאר למצוא דרך לספר את זה לשרת, מבלי צורך לשנות את כל הקוד הקיים, גילינו שכל הדפים שה - iframe מצביע אליהם משתמשים באותו master page, הוספנו את הקוד הבא:
 

<script>

 

    $(window).bind('beforeunload', function () {

        $(parent.document).find('iframe').each(function (index, value) {

            if (value.contentWindow == window) {

                var side = $(value).attr('data-side');

                $.cookie('side', side);

            }

        });

    });

</script>

 
נרשמנו לאירוע beforeunload שיקרה כל פעם לפני כל post back ולפני עזיבת העמוד (על ידי לינק לדף אחר וכד'), נמצא את העמוד שמכיל את כל ה - iframes בעזרת הפיכת ה - document של ה -  parent לאובייקט jquery, נחפש את כל ה - ifrmae ונרוץ עליהם בלולאה.
 
נבדוק האם החלון שנמצא בתוך ה - ifrmae הוא אכן החלון שלנו (החלון שמתוכו מתבצע ה - post back)
 
במידה וכן, נוציא את הערך שנמצא במאפיין data-side (ניסינו בהתחלה להשתמש בפונקציית data של jquery בצורה הבאה:

var side = $(value).data('side');

הבעייה שלאחר מספר post back איכשהו לא קבלנו את המידע הנכון - ולכן חזרנו ל - attr הישן והטוב)
 
כעת שמרנו את הערך ב - cookie בשם side (היות שבכל request כל ה - cookies נשלחים לשרת).
 
 
בצד השרת הגדרנו enum שנראה כך:
 

public enum Side

{

    LeftTop,

    RightTop,

    LeftButtom,

    RightButtom

}

 
בקוד הוספנו ב - page_load (של מחלקת ה - PageBase  - המחלקה שכל הדפים בפרוייקט יורשים מהם) את הקוד הבא:
 

HttpCookie sideCookie = Request.Cookies["side"];

if (sideCookie != null)

{

    Side side = (Side)Enum.Parse(typeof(Side), sideCookie.Value);

}

 
כעת ניתן היה לשאול בכל מקום בקוד מהיכן נעשה ה - post back. (ספציפית שם בפרוייקט הוספנו מנגנון שכל שמירה ב - Session תעבור דרך פונקצייה שמשתמשת ב - side כדי לשמור את ה - session לאותו side (כלומר הוספנו את ה - ToString של המשתנה side לשם ה - session) כך שניתן תמיד להוציא את המידע הרלוונטי לאותו side)
 
 
אחרי קצת דיבגינג גילינו שחלק מה - post back לא מגיעים לקוד ה - beforeunload, בדיקה קטנה גילתה שמדובר ב - post back מתוך update panel, מה שגרם לנו להוסיף את הקוד הבא:
 

<script>

    var prm = Sys.WebForms.PageRequestManager.getInstance();

    prm.add_beginRequest(function (requestManager, args) {

        saveSideInCookie();

    });

 

</script>

 
כמובן שהוצאנו את הקוד מלמעלה לפונקצייה שנקראת saveSideInCookie, חשוב בנוסף לשים לב שהסקריפט הזה צריך לשבת אחרי ההגדרה של ה - Script Manager.
 
הקוד הזה יגרום לכך שכל async post back ישמור את המידע ב - cookie.

Introduction to ASP.NET MVC 3 - Part 3

 

בהמשך לפוסטים שיכניסו אתכם לעולם ה - Asp.net MVC, נמשיך לפתור תרגילים כדי ללמוד את הטכנולוגיה.

תרגיל מספר 4 - הודעות שגיאה על ערכים בלתי תקינים.
אמנם בפרק זה אין נגיעה ממשית ב - asp.net mvc, אך היות שסדרה זו מיועדת לנכנסים לעולם ה - web ללא רקע מוקדם, חלק מהתרגילים יגעו בעולמות שונים מעולם ה - web.
 
תיאור התרגיל:
הצגת הודעות שגיאה למשתמש בעזרת אנימצייה של הודעה (בסגנון ההודעות של Gmail).
 
מטרת התרגיל:
עבודה עם css, עבודה עם jQuery. עבודה עם timers.

שלבים:
  • הוסיפו לדף ה - Layout.cshtml אלמנט מסוג div שימורכז לאמצע המסך ויוצמד למעלה (בעזרת css – שימוש ב – position, עצבו אותו כך שיראה דומה למה שמוצג בגוגל).
  • הגדירו לו css שיסתיר את האלמנט.
  • כתבו פונקציית JS המקבל טקסט, ועושה את הפעולות הבאות:
    • בעזרת jQuery מוצא את ה – div.
    • כותב לתוכו את הטקסט המתקבל.
    • משתמש בפונקצית slideDown כדי להציג אותו באנימצייה.
    • לאחר חמש שניות (בעזרת setTimeout) מסתיר את האלמנט עזרת שימוש ב – slideUp)
  • שנו את מימוש דף הלוגין, שבזמן לחיצה על הלחצן – אם חסר ערכים שלא יציג alert אלא ישלח טקסט לפונקצייה שכתבתם זה עתה.
 
 
כפי שכבר כתבתי, פוסט זה ואחרים בסדרה זו, יכניסו אתכם לעולם פיתוח ה - web בהתבסס על asp.net mvc, והיות שבטכנולוגייה זו משתמשים הרבה ב - javascript ו - jquery חלק מהתרגילים יכללו תירגול בנושא זה.
 
אז לפיתרון. (את הקוד המלא ניתן להוריד כאן)
 
 
נוסיף את קטע קוד ה - html הבא בדף המסטר. (מיד לאחר תגית ה - body)
 
 

<div id="msg">

</div>

 
כעת נוסיף קטע css שישפיע על האלמנט msg
 

<style>

    #msg

    {

        position: absolute;

        width: 220px;

        background-color: Yellow;

        border: 1px solid black;

        left: 50%;

        margin-left: -110px;

        padding: 2px;

        font-family: Arial;

        border-radius: 0px 0px 5px 5px;

        text-align: center;

        top: 0px;

        border-top-style: none;

        display: none;

    }

</style>

 
ונכתוב את קוד ה - js שיציג את המידע בתוך האלמנט שיגיע עם אנימציה, ולאחר כמה שניות יעלם.
 

<script type="text/javascript">

 

    function showMessage(msg) {

        $('#msg').text(msg).slideDown();

        setTimeout(function () { $('#msg').slideUp(); }, 4000);

    }

</script>

 
 
כל מה שנשאר לעשות, זה במתודה validate במקום alert להשתמש ב - showMessage.
 
 

Introduction to ASP.NET MVC 3 - Part 2

בהמשך לפוסטים שיכניסו אתכם לעולם ה - Asp.net MVC, נמשיך לפתור תרגילים כדי ללמוד את הטכנולוגיה.
 
 
תרגיל מספר 3 - הוספת דף רישום.
 
תיאור התרגיל:
הוספת דף רישום המאפשר להוסיף משתמשים חדשים במערכת, ניתן יהיה להגיע לדף הרישום מתוך דף הלוגין, המשתמש ימלא את הנתונים, ישלח אותם לשרת ובמידה והכול תקין יוסיף אותם למערכת.
 
מטרת התרגיל:
היכרות עם ה – html helpers, היכרות עם ה – attributes המאפשרים ולידציות אוטומטיות, הוספת אובייקטים לבסיס הנתונים, עבודה עם ולידציות גם בצד הלקוח.
 
שלבים:
  1. הגדירו אובייקט חדש המכיל רק את השדות אותם המשתמש אמור למלאות (שם, סיסמא, סיסמא שוב, גיל, דוא"ל).
  2. הוסיפו Attribute שונים על המאפיינים ([Required], [DataType] [Display] [Compare] [StringLength] ואחרים, כדי להגדיר כיצד הם אמורים להגיע מצד הלקוח)
  3. הוסיפו View חדש המאפשר קליטה של פרמטרים מתאימים עבור אותם מאפיינים, השתמשו ב – Helpers שונים של @Html (BeginForm, LabelFor, TextBoxFor)
  4. בתוך ה – form הוסיפו לחצן מסוג sumbit.
  5. הגדירו את ה – model של ה – view, לאובייקט החדש שיצרתם.
  6. הוסיפו Action חדש בשם Register המחזיר את ה – view.
  7. הוסיפו Action חדש בשם Register המקבל מופע של האובייקט החדש שיצרתם, ומוגדר כ – HttpPost (בעזרת attribute).
  8. בתוך הקוד של ה – action, חלצו את המידע מתוך האובייקט, וייצרו אובייקט חדש של EF, כשאתם מעתיקים את המידע מתוך אובייקט המודל, והוסיפו אותו לבסיס הנתונים.
 
תוכלו כמובן להוריד את דוגמת הקוד מהפוסט הקודם, ולהמשיך משם, מומלץ כמובן לנסות לפתור לבד, אבל אם מאוד תרצו תוכלו להוריד את דוגמת הקוד מכאן.
 
 
נתחיל לפתור את התרגיל.
 
ראשית כדי לעבוד בצורה נכונה, ברוב המקרים לא נרצה לעבוד ישירות מול ה - View עם האובקייטים של הלוגיקה, (Entity Framework וכד') - ואני מדגיש ברוב המקרים ולא בכל המקרים.
 
אחד מהסיבות להגדיר אוביקט שמקשר בין ה - View לבין המודל, הוא כדי להשתמש במאפיינים שונים של mvc עבור האובייקטים המקושרים ל - View.
 
במקרה שלנו נכתוב אובייקט כזה:
 
 

public class UserViewModel

{

    [Required]

    public string Name { get; set; }

 

    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]

    [Required, DataType(DataType.Password)]

    public string Password { get; set; }

 

    [Required, DataType(DataType.Password), Display(Name = "Confirm new password")]

    [Compare("Password", ErrorMessage = "The new password and confirmation password do not match.")]

    public string ConfirmPassword { get; set; }

 

    [Required, DataType(DataType.EmailAddress)]

    public string Email { get; set; }

 

    [Required]

    public int Age { get; set; }

 

    public static implicit operator User(UserViewModel uvm)

    {

        return new User()

        {

            Age = uvm.Age,

            Email = uvm.Email,

            Name = uvm.Name,

            Password = uvm.Password

        };

    }

}

 
 
נפרק את האובייקט, יש לנו מאפיין שנקרא Name, שיש עליו attribute המגדיר ששדה זה הוא חובה - המשמעות שלו היא, ש - asp.net mvc תעביר ולידצייה בזמן submit האם שלחנו את המידע (מזכיר מאוד את הולידציות של asp.net web forms).
 
במאפיין הבא (Password) גם נגדיר מספר מקסימלי ומנימילי לתווים, והודעת שגיאה (אם רוצים לעבוד multi language ניתן לשלוח שם של Resource וה - key הרלוונטי)
 
וכן הלאה על כל המאפיינים.
 
בחלק האחרון של המחלקה, נגדיר cast opertaor היודע להמיר בין אובייקטי מסוג UserViewModel לבין אובייקטי של Entity Framework (ל - DB צריך לשלוח אובייקט של EF)
 
את הקובץ נוסיף פיזית תחת תיקיית Model.
 
 
כעת נוסיף View בשם Register תחת תיקיית Start, הקובץ יכיל את הוקד הבא: (לא מופיע כאן כל הקוד, תוכלו להוריד את הקוד המלא מהלינק לעיל)
 
ראשית נגדיר מיהו המודל של ה - View (שורה ראשונה)
 
 
 

@model MvcExercise3.Models.UserViewModel

 
ברגע שהגדרנו את המשתנה model (באותיות קטנות) יהיה לנו לכל אורך ה - View משתנה בשם Model (אות גדולה) המכיל מופע של המחלקה (אם ה - Controller החזיר מופע שלו).
 
 
כעת נרצה לייבא קבצי JS יחודיים ל - View זה, בפוסט הראשון הגדרנו Section מיוחד בשם Script המאפשר לנו להוסיף בדיוק את זה.
 

@section scripts

{

    <script src="@Url.Content("~/Scripts/jquery-1.7.1.js")" type="text/javascript"></script>

    <script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>

    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>

}

 
המידע בתוך ה - scripts section ייכנס למיקום ב - head שהוגדר שם RenderSection עבורו.
 
בכל מקום ב - mvc שנרצה להשתמש בלינק למקום כלשהו נשתמש ב - Url.Content (בדומה ל - ResolveUrl של WebForms)
 
נוסיף את שלושת קבצי ה - js הללו - כדי ש - asp.net תשתמש במנגנון המובנה של jquery validate כדי לבצע ולידציות בצד הלקוח עוד לפני הפנייה לשרת)
 
 
כעת נוסיף את הקוד הבא:
 

@using (Html.BeginForm())

{

    @Html.ValidationSummary(true, "Account creation was unsuccessful. Please correct the errors and try again.")   

    <div>

        <fieldset>

            <legend>Account Information</legend>

 

            <div>

                @Html.LabelFor(m => m.Name)

            </div>

            <div>

                @Html.TextBoxFor(m => m.Name)

                @Html.ValidationMessageFor(m => m.Name)

            </div>

 

            <div>

                @Html.LabelFor(m => m.Email)

            </div>

            <div>

                @Html.TextBoxFor(m => m.Email)

                @Html.ValidationMessageFor(m => m.Email)

            </div>

 

            <div>

                @Html.LabelFor(m => m.Password)

            </div>

            <div>

                @Html.PasswordFor(m => m.Password)

                @Html.ValidationMessageFor(m => m.Password)

            </div>

 

            <div>

                @Html.LabelFor(m => m.ConfirmPassword)

            </div>

            <div>

                @Html.PasswordFor(m => m.ConfirmPassword)

                @Html.ValidationMessageFor(m => m.ConfirmPassword)

            </div>

 

            <p>

                <input type="submit" value="Register" />

            </p>

        </fieldset>

    </div>

 

    @ViewBag.Message

}

 
 
Html הוא Helper המכיל מתודות שייצרו עבורנו פקדי html.
 
לדוגמא:
 

@Html.LabelFor(m => m.Name)

ייצור span היכיל את הטקסט Name, בעוד שהשורה הבאה:
 

 @Html.LabelFor(m => m.ConfirmPassword)

 
תוסיף span עם הטקסט שמופיע בפרמטר Name ב - Attribute של Display על המאפיין ConfirmPassword.
 
ה - m הוא הגדרה של UserViewModel.
 
וכן הלאה לכל המאפיינים - כמו למשל:
 

@Html.ValidationMessageFor(m => m.Email)

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

@ViewBag.Message

 
בעזרת ViewBag נוכל לשלוח כל דבר (כמעט) מה - Controller ל - View (מאוד דומה ל - HttpContent.Current.Items, רק שכאן מדובר באובייקט מסוג dynamic, כך שלא צריך לעשות casting)
 
 
כעת ב - Contrller נוסיף שני מתודות בשם Register
 

public ActionResult Register()

{

    return View();

}

 

[HttpPost]

public ActionResult Register(UserViewModel user)

{

    if (ModelState.IsValid)

    {

        using (var context = new DatabaseEntities())

        {

            context.Users.AddObject(user);

            context.SaveChanges();

        }

 

        ViewBag.Message = "User successfully added";

    }

 

    return View();

}

 
 
המתודה הראשונה תחזיר את ה - View עבור גלישה ראשונה.
 
נוסיף את הקוד הבא בדף ה - Index
 

@Html.ActionLink("Regiser", "Register", "Start")

 
שייצור אלמנט מסוג a עם הטקסט Register שיפנה ל - action בשם Regisetr ב - Start Controller.
 
המתודה השנייה תקבל אובייקט מסוג UserViewModel (הוא ייבנה אוטמטית מכל ה - inputs בטופס המכילים את ה - name כשם המאפיין).
 
נבדוק האם הולידציות עברו בהצלחה, ה - ModelState משמש אותנו כדי להעביר ולקבל מידע האם יש שגיאות (נוכל גם להוסיף מידע אליו כדי להציג ב - Validation Summary)
 
במידה והכול תקין, נוסיף לבסיס הנתונים את המשתמש החדש (המתודה AddObject מקבלת אובייקט מסוג DataModel.User - האובייקט מסוג UserViewModel עבר casting אוטומטי בעזרת הקוד שראינו למעלה)
 
 
 
לסיכום.
בתרגיל זה למדנו על עוד יכולות בסיסיות של asp.net mvc, ועבודה משתופת של Model View Controller, בפוסט הבא נתרגל קצת עבודה עם jQuery כדי להציג הודעות שגיאה יותר יפות בצד הלקוח.

Introduction to ASP.NET MVC 3 - Part 1

 

הקדמה: 
לפני כמעט חודשיים התחלתי לכתוב סדרת פוסטים לנכנסים לעולם ה - Asp.net mvc - הצהרתי שאכתוב פוסט יומי, לצערי לא כל כך עמדתי בזה :-) אשתדל מהיום לעמוד יותר בכיוון של הבטחה זו.
 
 
כפי שתארתי פוסטים אלו הם פוסטים מתגלגלים (כלומר כל פוסט תלוי בקודם), לכן מומלץ לקרוא ראשית את הפוסט הקודם ורק לאחר מכן להמשיך כאן.
 
בנוסף פוסטים אלו אינם כתובים כפי שאני כותב בדרך כלל את הפוסטים שלי, אלא בכל פוסט יש תרגיל (מבוסס על הקודם) עם הוראות וצעדים כיצד לפתור אותו - לאחר מכן (מומלץ קודם לנסות לבד) מובא הפיתרון לתרגיל עם ההסברים.
 
 
תרגיל מספר 2. - עדכון דף הלוגין מול בסיס הנתונים.
 
תיאור התרגיל: בהתבסס על התרגיל הקודם, בזמן הבדיקה יש לבדוק האם שם המשתמש והסיסמא נכונים מול ה - DB.
 
מטרת התרגיל: היכרות עם Entity Framework, עדכון בסיס הנתונים, ביצוע שאילתות LINQ.
 
שלבים:
  1. ייצרו בסיס נתונים חדש המכיל טבלת Users עם מספר עמודות (Id, Email, Name, password, Age) עמודת Id חייבת להיות מפתח ראשי ומוגדרת כ – Identity.
  2. הוסיפו לפרוייקט פריט מסוג Ado.net entity model, בעזרת ה – wizard הוסיפו את הטבלה החדשה.
  3. שנו את ה – controller שבודק האם שם המשתמש והסיסמא נכונים לשאילתת LINQ מול Entity Framework.
 

 
פיתרון:
את דוגמת הקוד ניתן להוריד מכאן.
 
פתחו את ה - Sql Server Managment Studio, הוסיפו בסיס נתונים חדש בעזרת לחיצה ימנית על Databases ולחיצה על New Database.
 
תנו שם כלשהו לבסיס הנתונים ולחצו על OK.
 
לאחר מכן גשו לבסיס הנתונים החדש שנוצר והוסיפו טבלה חדשה לפי ההוראות לעיל.
 
הנה הסקריפט לייצור הטבלה.
 

SET ANSI_NULLS ON

GO

 

SET QUOTED_IDENTIFIER ON

GO

 

CREATE TABLE [dbo].[User](

[Id] [int] IDENTITY(1,1) NOT NULL,

[Name] [nvarchar](50) NOT NULL,

[Age] [int] NOT NULL,

[Passsord] [nvarchar](50) NOT NULL,

[Email] [nvarchar](50) NOT NULL,

 CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED

(

[Id] ASC

)

) ON [PRIMARY]

 
 
לאחר שהטבלה מוכנה, בפרוייקט הקודם ב - Visual Studio הוסיפו פריט חדש מסוג Entity Framework, ובחרו ב - Generate model from database, ובחרו את בסיס הנתונים שזה עתה יצרנו.
 
כעת גשו ל - StartController במתודת Index ושנו את המימוש לקוד הבא:
 

[HttpPost]

public ActionResult Index(string name, string password)

{

    if (name == null || name.Trim() == string.Empty || password == null || password.Trim() == string.Empty)

    {

        ViewBag.Error = "Parameters required";

        return View();

    }

 

    using (var context = new DatabaseEntities())

    {

        if (!context.Users.Any(x => x.Name == name && x.Password == password))

        {

            ViewBag.Error = "User name or password incorrect";

            return View();

        }

    }

 

    return View("Welcome");

}

 
 
במקום לבדוק בצורה סטטית את השם והסיסמא, נייצר מופע של DatabaseEntities (שזה השם שנתתי ל - Model) נבדוק האם קיים שם שם משתמש וסיסמא (כמובן שנדאג להכניס ידנית מידע לבסיס הנתונים).
 
ההבדל המרכזי בין הקוד הסטטי לקוד מול ה - EF, הוא יצירת מופע של המודל ושימוש במתודת Any שמקבלת כפרמטר תנאי כלשהו ומחזירה משתנה בולייאני האם התנאי קיים ב - DB.
 
 
בפוסט זה אין הרבה מה ללמוד בעולם ה - MVC, אך הוא הכנה לקראת הפוסטים הבאים בהם נרצה להוסיף דף רישום בו נוכל מתד הלקוח להרשם למערכת.

BindingSource on remove item event

 

יצא לי לאחרונה להשתמש בפקד BindingSources ב - WindowsForms Application - תפקיד הפקד הוא לשמש מכניזם המאפשר לממש בקלות קישור בין אובייקטי המידע שלנו לבין פקדים בטופס.
 
אחד הדברים המעניינים (והמעצבנים) שגיליתי - הוא שאם אני רוצה לדעת מתי נמחק איבר כלשהו מהרשימה (נניח שקשרתי את ה - BindingSource ל - Grid, והמשתמש מוחק שורה מההגריד), ניתן להירשם לאירוע ListChanged כך:
 
 

Binder.ListChanged += binder_ListChanged;

 
ובמתודה לבדוק
 

private void binder_ListChanged(object sender, ListChangedEventArgs e)

{

    if (e.ListChangedType == ListChangedType.ItemDeleted)

    {

        //....

    }

}

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

public class EventBindingSource : BindingSource

{

    public event EventHandler<CancelEventArgs<object>> ItemRemoving;

 

    public EventBindingSource()

    {

 

    }

 

    public EventBindingSource(IContainer container)

        : base(container)

    {

 

    }

 

    public EventBindingSource(object dataSource, string dataMember)

        : base(dataSource, dataMember)

    {

 

    }

 

    public override void Remove(object value)

    {

        if (EnsureRemoving(value))

            base.Remove(value);

    }

 

    public override void RemoveAt(int index)

    {

        object value = this[index];

 

        if (EnsureRemoving(value))

            base.RemoveAt(index);

    }

 

    private bool EnsureRemoving(object value)

    {

        CancelEventArgs<object> cea = new CancelEventArgs<object>(value);

        OnItemRemoving(cea);

 

        return !cea.Cancel;

    }

 

    protected virtual void OnItemRemoving(CancelEventArgs<object> e)

    {

        if (ItemRemoving != null)

        {

            ItemRemoving(this, e);

        }

    }

}

 
הגדרתי ארוע שמקבל CancalEventArgs<T (זהו הרחבה של מה שמתואר כאן) - הנה הקוד שלו
 

public class CancelEventArgs<T> : CancelEventArgs

{

    public T Data { get; private set; }

 

    public CancelEventArgs(T data)

    {

        Data = data;

    }

}

 
לפני מחיקה של איבר מהרשימה, נודיע לכל מי שנרשם שאיבר הולך להימחק - במידה ואף אחד לא ביטל את המחיקה, אכן נמחק את האיבר (בעזרת קריאה ל - base).
 
כמובן שעכשיו כל מי שרוצה לדעת מה נמחק יוכל.
 
ניתן להרחיב את המחלקה על ידי הגדרת אירוע בשם ItemRemoved ולשלוח את המידע למי שמעוניין לדעת מה נמחק רק לאחר המחיקה.
Posted: Apr 03 2012, 05:04 AM by Shlomo | with no comments
תגים:, , ,

פוסט מספר 500 - והפתעה ממיקרוסופט

 

איזה כיף לכתוב את פוסט מספר 500, אני חושב שזה בהחלט מספר הראוי להתכבד בו, הפוסט הראשון שלי היה ב - 10 לנובמבר 2008, והיום ב - 1 לאפריל 2012 אני נמצא במספר 500.
 
פתחתי מקודם את המייל וראיתי שה - MVP שלי חודש, אז תודה רבה לכל האנשים הנהדרים במיקרוספט, אני מקווה להמשיך לכתוב, לתרום לקהילה ולענות בפורומים ביתר שאת.
 
וכמובן אני חייב הסבר לירידה בהשתתפות בפורומים של מיקרוסופט, וזאת עקב סיבה משמחת - הולדת בני (החמישי) לפני כמה שבועות, מה שגורם לי ב"ה לישון הרבה פחות בלילה ולהיות הרבה פחות פעיל במשך היום.
 
שלמה - הרב דוטנט
Posted: Apr 01 2012, 06:53 PM by Shlomo | with 4 comment(s)
תגים:

עצלנות של מפתחים ואבטחת מידע - והפעם כיצד לעקוף את הבדיקה שטלריק עושים האם קניתם את המוצר (2)

 

אני יודע שהפוסטים הללו לא בהכרח קשורים כך כך לאבטחת מידע - אבל בעקיפין אם נכתוב קוד לא נכון שניתן לכתוב קוד שיעקוף אותו סביר להניח שזה יכול גם לגרום לבעיות באבטחת מידע.
 
באחד הפוסטים סיפרתי כיצד לעקוף את ההודעה המעצבנת של טלריק בזמן פיתוח אם אין לכם רישיון, בפוסט זה אני אראה כיצד לעקוף אותם בחבילה שלהם לעולם ה - Windows Forms.
 
 
רק להבהיר שחלילה לגנוב מהם, ותמיד כשאני מעלה מוצר לאויר שמשתמש בטלריק אני דואג שיהיה רשיון, המטרה בפוסט היא לבהיר כיצד ניתן לכתוב קוד גרוע - (וגם איך לעשות לנו כמפתחים חיים קלים יותר בשלב הפיתוח - וכמובן איזה כיף שיש Reflector).
 
 
אז ככה, במידה ויש לכם את החבילה לעולם ה - Windows forms, סביר להניח שמידי פעם הייתם מקבלים את החלון הבא:
 
telerik win fors
 
 
האמת שזה ממש מעצבן, אז כרגיל פתחתי את ה - Reflector והתחלתי לשוטט - הפעם לקח לי הרבה יותר זמן מהרגיל, כעבור שלוש שעות שיטוט ב - Reflector מצאתי את הקוד הבא באובייקט שנקרא RadElementTree.
 
 

private void CheckLicense()

{

    if ((RadControl.licenseCount == -1) && (numberOfEvalMsgsShown < 3))

    {

        RadControl.licenseCount = new Random().Next(0, 10);

        if (RadControl.licenseCount == 1)

        {

            Assembly assembly = null;

            foreach (Assembly assembly2 in AppDomain.CurrentDomain.GetAssemblies())

            {

                if (assembly2.FullName.Contains("Telerik.WinControls.UI"))

                {

                    assembly = assembly2;

                    break;

                }

            }

            if (assembly != null)

            {

                Type type = assembly.GetType("Telerik.WinControls.UI.Licensing.RadEvaluationForm");

                object target = Activator.CreateInstance(type);

                type.InvokeMember("ShowDialog", BindingFlags.InvokeMethod, null, target, null);

            }

            else

            {

                using (EvaluationForm form = new EvaluationForm())

                {

                    form.ShowDialog();

                }

            }

            numberOfEvalMsgsShown++;

        }

    }

}

 
 
הקוד בודק האם יש רשיון (לא הצלחתי למצוא היכן הם מעדכנים את הערך של המשתנה licenceCount - ההימור שלי שהרשיון כנראה מביא איתו dll חדש ללא הפונקצייה הזאת - אבל אני לא בטוח)
לאחר מכן האם כבר הוצגו שלוש הודעות על גרסת הניסיון.
 
במידה ואין רשיון והם עדיין לא עצבנו את המפתח מספיק, הם בודקים האם ה dll שלהם טעון בזיכרון - במידה וכן הם יוצרים מופע של המסך שהצגתי למעלה ומפעילים אותו בעזרת Reflection, אחרת הם מציגים חלון אחר (דומה מאוד רק ללא עיצוב של telerik).
 
 
אחרי שמצאתי את הקוד - לעקוף אותו זה כבר עניין פשוט, היות שהחכמים האלו משתמשים ב - Reflection כדי לייצר את המופע - ויותר מכך הם בודקים האם ה - dll טעון לזיכרון לפי השם שלו, יצרתי dll חדש שקראתי לו Telerik.WinControls.UI.Crack, הוספתי לו את הקוד הבא:
 

namespace Telerik.WinControls.UI.Crack

{

    public class RadEvaluationForm

    {

        public static void Crack()

        {

 

        }

    }

}

 

namespace Telerik.WinControls.UI.Licensing

{

    public class RadEvaluationForm

    {

        public void ShowDialog()

        {

 

        }

    }

}

 
בפונקצית Main לפני הכול קראתי ל - Crack
 

RadEvaluationForm.Crack();

 
מה שקורה הוא שהיות שהשימוש בפונקציית Crack קורה לפני כל דבר אחר, ה - dll שלי (שכזכור עומד בבדיקה שהם עשו - Contain על השם שלו) נטען ראשון לזיכרון, ולכן כשהם יבקשו מופע של החלון הם יקבלו את המחלקה שלי שפונקציית ה - ShowDialog לא עושה כלום.
 
דרך אחרת - היא פשוט להריץ קוד שיבצע את הדבר הבא:
 

Type type = typeof(RadElementTree);

FieldInfo evalMsgInfo = type.GetField("numberOfEvalMsgsShown", BindingFlags.NonPublic | BindingFlags.Static);

evalMsgInfo.SetValue(null, 50);

 
 
ואז למעשה, נגרום להם לחשוב שהם כבר הציקו לנו מספיק.
More Posts Next page »