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

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

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

December 2011 - Posts

הרצת כמה WorkerRole בפרוייקט azure אחד

 

בהמשך לפוסט שהסביר כיצד להריץ כמה Web Roles במחשב אחד, נראה כעת כיצד להריץ כמה Worker Roles במחשב אחד.
 
בשונה מ - Web Roles שכדי להריץ אותם ממחשב אחד צריך רק לעשות שינויים בקובץ הקונפיג, ב - Worker Role זה כבר דורש שינוי קוד.
 
אני משתמש בפיתרון שנכתב כאן (כדאי לקרוא את ההסברים שם) - את דוגמת הקוד וה - framework שלהם אפשר להוריד כאן
 
 
 
כדי להשתמש בקוד שלהם צריך לייצר WorkerRole שיורש ממחלקה בשם ThreadedRoleEntryPoint (מחלקה שהם כתבו)
כל WorkerRole צריך לרשת מ - WorkerEntryPoint. ולא מ - RoleEntryPoint
 
 
וכעת ניתן לכתוב את הקוד הבא ב - WorkerRole (שאמור להכיל את שאר ה - Roles)
 

public class WorkerRole : ThreadedRoleEntryPoint

{

    public override bool OnStart()

    {

        List<WorkerEntryPoint> workers = new List<WorkerEntryPoint>();

 

        workers.Add(new RaderWorkerRole());

        workers.Add(new LogWorker());

 

        return base.OnStart(workers.ToArray());

    }

}

 
 
וזהו. (שוב - מומלץ לקרוא את הפוסט המקורי ולהבין איך זה עובד מאחורי הקלעים - זה לא מסובך)
Posted: Dec 28 2011, 02:47 PM by Shlomo | with no comments
תגים:,

הרצת כמה WebRole בפרוייקט azure אחד

 

הרבה פעמים יש לנו כמה פרוייקטים שאמורים להתארח על IIS, בסביבה רגילה נארח את כולם על אותו IIS בשרת אחד, ב - azure העניינים קצת יותר מסובכים, היות שכברירת מחדל כל web role אמור לרוץ על מחשב משלו - מה שכמובן הרבה פעמים מיותר ועולה כסף.
 
יש כמה פתרונות לזה - אני אדגים כאן פיתרון בעזרת הגדרה של Virtual Application.
 
Virtual Application הוא בעצם Application רגיל שאנחנו מכירים ב - IIS - מה שמיוחד זה כיצד להגדיר אותו שירוץ כמו שצריך ב - azure.
 
 
ראשית נכנס לקובץ ServiceDefinition.csdef, נמחק את הגדרות ה - WebRoles שאנחנו לא מעוניינים שיעלו כמחשב נפרד.
 
לאחר מכן תחת Site (לפני הגדרת ה - Bindings) בהגדרה של הפרוייקט שאנחנו מתעדים להעלות אותו כמחשב נכתוב את הקוד הבא:
 

<VirtualApplication name="a10" physicalDirectory="c:\........">

</VirtualApplication>

 
כמובן שה - physicalDirectory צריך להכיל את המיקום המדוייק בו נמצא הפרוייקט, המאפיין name מכיל את ה - alias דרכו יגלשו לאתר.
 
 
כמה נקודות שצריך לזכור, היות שה - Site עצמו מפנה לאיזשהוא אפליקציה (מה שמוגדר במאפיין Name של ה - WebRole) זה אומר שההגדרות הקונפיג של פרוייקט ה"אבא" מחלחלים לכל ה - Virtual Applications וצריך להזהר משכפולים (כמו למשל עם אותו ConnectionString יהיה מוגדר בשניהם, יגרום להתרסקות ה - Virtual Application).
 
עוד דוגמא לבעייה - כיום azure לא תומך ב - MVC3, אם ברצונכם בכל זאת לעבוד איתו, צריך להגדיר על רשימת ה - dll's הבאים Copy Local עם הערך true.
 
 
Microsoft.Web.Infrastructure
System.Web.Helpers
System.Web.Mvc
System.Web.Razor
System.Web.WebPages
System.Web.WebPages.Deployment
System.Web.WebPages.Razor
 
במידה ופרוייקט ה"אבא" יהיה MVC3 - כל שאר ה - Virtal Applications יצטרכו להכיל את כל אותם dll's אע"פ שהם כלל לא ישתמשו בהם (לדוגמא wcf service).
 
פתרון טוב לבעיות אלו, יהיה להגדיר את פרוייקט ה"אבא" כפרוייקט ריק שאין בו כלום, ולהגדיר את כל שאר הפרוייקטים כ - Virtual Applications.
 
 
פתרון אחר הוא להגדיר כמה Sites, אבל על זה אכתוב בהזדמנות אחרת.
Posted: Dec 25 2011, 08:48 AM by Shlomo | with 1 comment(s)
תגים:, , ,

קריאת הערכים מתוך קבצי קונפיג בפרוייקט שיכול לרוץ בענן

 

כשאנחנו מפתחים אפליקציות web אנחנו משתמשים בקובץ הקונפיג כדי להגדיר את ה - Connection String והגדרות שונות ב - App Settings, במעבר ל - azure הדברים מסתבכים קצת יותר, ניתן עדיין להגדיר את כל ההגדרות בקובץ ה - web.config, אבל זה אומר שבכל שינוי נצטרך לעשות Upload מחדש למערכת - מה שכמובן לא מומלץ.
 
האופציה האחרת היא להשתמש בקבצי הקונפיג של azure.
 
ניתן כמובן להשתמש עם חלון המאפיינים של הפרוייקט (תחת תיקיית ה - Roles, לחיצה כפולה על ה - Role המתאים) - אבל כדי להבין מה עושים תמיד עדיף בהתחלה לכתוב לבד.
 
בפרוייקט ה - azure שלכם ניתן לפתוח את הקובץ ServiceDefinition.csdef, בתוך ה - Role המתאים ניתן לכתוב את המקטע הבא:
 

<ConfigurationSettings>

  <Setting name="DBConnectionString" />

</ConfigurationSettings>

 
 
כעת נעבור לקבצים ServiceConfiguration.Local.cscfg, ServiceConfiguration.Cloud.cscfg אשר הם מכילים את הערכים עצמם.
קובץ Local מכיל את ההגדרות כשהמערכת רצה עם האימולטור, וקובץ ה - Cloud מכיל את ההגדרות כשהמערכת תהיה בענן.
 
נכתוב את הקוד המתאים לפי הסביבה
 

<ConfigurationSettings>

  <Setting name="DBConnectionString" value="CONNECTION_STRING" />

</ConfigurationSettings>

 
כעת בקוד כשנרצה לקרוא את הערך נוכל לכתוב משהו כזה:
 

var connStr = RoleEnvironment.GetConfigurationSettingValue("DBConnectionString");

 
 
כדי להגדיר בנוחות בלי צורך לכתוב xml, ניתן לפתוח את ה - Role המתאים, לבחור בטאב Settings ולהוסיף כל הגדרה שתרצו, כדי להגדיר את הערכים צריך לבחור בקומבו (בחלק העליון של המסך) עבור איזה סביבה רוצים להגדיר את הערכים.
 
 
הבעייה עם הקוד הזה - שהוא מכריח אותנו להריץ לוקלית את האפליקציה עם האימולטור, לפעמים נרצה להריץ אותו תחת IIS כמו שהכרנו בעבר, מה שיגרום להתרסקות האפליקצייה בזמן ניסיון לקריאה מה - RoleEnvironment.
 
כדי לפתור זאת - כתב ידידי היקר חיים בריקמן את הקוד הבא:
 

public static class ConfigFetcher

{

    private static bool isRunningOnCloud = false;

 

    static ConfigFetcher()

    {

        isRunningOnCloud = RoleEnvironment.IsAvailable || RoleEnvironment.IsEmulated;

    }

 

    public static string Fetch(string name)

    {

        string val;

        if (isRunningOnCloud)

        {

            val = FromAzure(name);

        }

        else

        {

            val = FromConfig(name);

        }

        return val;

    }

 

    private static string FromAzure(string name)

    {

        try

        {

            var connStr = RoleEnvironment.GetConfigurationSettingValue(name);

            return connStr;

        }

        catch

        {

            return FromConfig(name);

        }

 

    }

 

    private static string FromConfig(string name)

    {

        var connStr = ConfigurationManager.ConnectionStrings[name];

 

        if (connStr != null)

        {

            return connStr.ConnectionString;

        }

 

        var setting = ConfigurationManager.AppSettings[name];

 

        if (setting != null)

        {

            return setting;

        }

 

        throw new ConfigurationErrorsException(name);

    }

}

 
 
כעת בכל מקום באפליקציה שצריך ערך מתוך הקונפיג נשתמש במחלקה זו כדי להביא את הנתונים לפי הסביבה המתאימה
Posted: Dec 19 2011, 10:20 AM by Shlomo | with 1 comment(s)
תגים:, ,

ניהול בסיסי של תהליך כניסה והרשאות לאתר

 

ניהול משתמשים והרשאות הוא אף פעם לא מהדברים הקלים, בפוסט זה נראה כיצד נשתמש במנגנון הבסיסי של asp.net כדי לוודא שאי אפשר להגיע לשום דף לפני שעברו תהליך login בהצלחה, בנוסף נראה כיצד אנחנו "אומרים" לשרת שהמשתמש עבר לוגין.
 
 
הדבר הראשון שנרצה לעשות זה להגדיר שהמשתמש לא יכול לגלוש לשום מקום לפני תהליך login, כדי לעשות זאת נכתוב בקובץ הקונפיג תחת system.web את המקטע הבא:
 

<authorization>

  <deny users="?"/>

</authorization>

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

<authentication mode="Forms">

  <forms loginUrl="login.aspx"

          defaultUrl="main.aspx"

          name="myLoginCookieName"

          timeout="10080"

          slidingExpiration="true"></forms>

</authentication>

 
נעבור על המאפיינים.
לא נכנס לעומק על המשמעות של המאפיין mode, רק אספר שבמידה והוא הוגדר כ - forms זה אומר שהאפליקציה שלנו אחראית על המשתמשים והיא מנהלת את תהליך הלוגין.
 
בתוך התגית forms המאפיין loginUrl מכיל את הכתובת לדף שבו מבצעים את תהליך הלוגין, חייבים להגדיר לו זאת מכיוון שהגדרנו במקטע הקודם שאסור לגלוש לאתר במידה והמשתמש אנונימי, ולכן ההגדרה של loginUrl מאפשרת עמוד אחד שאליו ניתן לגלוש. (זה הפרמטר היחיד שחובה להגדיר)
 
המאפיין defaultUrl מגדיר את הדף שאליו המשתמש יועבר לאחר תהליך הלוגין.
 
המאפיין name מגדיר את שם העוגייה שתשמור את המידע שאומר שהמשתמש עבר login בהצלחה (מומלץ לשנות את שם העוגייה - ברירת המחדל היא .ASPXAUTH)
 
המאפיין timeout קובע כמה זמן העוגייה תישמר (ברירת המחדל היא 30 דקות, בדוגמא זה הוגדר כשבוע) - חשוב להבין שההגדרה בקונפיג היא לא ההגדרה של "זכור אותי", בברירת מחדל העוגיה נשמרת בזיכרון של הדפדפן ולא בקבצים, מה שאומר שכל דפדפן חדש שיפתח יצטרך לוגין בפני עצמו, בהמשך נראה כיצד להגדיר "זכור אותי".
 
המאפיין slidingExpiration מגדיר האם הזמן שהוגדר לעוגייה היא אבסולוטית (כלומר 30 דקות מהרגע הראשון) או מהכניסה האחרונה של המשתמש (כלומר בכל PostBack הזמן מתחיל להספר מחדש).
 
 
כעת נכתוב את דף login.aspx, נניח שיש לכם שני תיבות טקסט ולחצן. נכתוב את הקוד הבא:
 

protected void Login_Click(object sender, EventArgs e)

{

    if (LoginValidate(txtName.Text, txtPassword.Text))

    {

        FormsAuthentication.RedirectFromLoginPage("name", true);

    }

}

 
 
כמובן שהפונקציה LoginValidate היא פונקציה שנצטרך לממש כך שתבדוק האם שם המשתמש והסיסמא חוקיים, במידה וכן, נשתמש בפונקציה שמעבירה את המשתמש לדף שהוגדר כ - defaultUrl, הערך true מגדיר את "זכור אותי" כך שבפעם הבאה שהמשתמש יגלוש לאתר הוא לא יצטרך לעבור login, במידה ותרצו שבכל פעם שהוא נכנס לאפליקציה הוא יעבור תהליך login, הגדירו ערך זה כ - false.
 
 
במידה ואתם רוצים להחליט לאיזה דף לגלוש, תוכלו לכתוב את הקוד הבא:
 
 

protected void Login_Click(object sender, EventArgs e)

{

    if (LoginValidate(txtName.Text, txtPassword.Text))

    {

        FormsAuthentication.SetAuthCookie("name", true);

 

        // redirect...

    }

}

 
 
פוסט זה הינו הבסיס לתהליך login, ולא דברתי כאן על מנגנון ה - Membership של asp.net - אולי בפוסט ארחיב ארחיב עליו.

log4net and azure

 

בדרך כלל אני משתמש בתשתית log4net כדי לשמור לוגים, אני אוהב לשמור אותם אותם לקבצים ואני משתמש ב - RollingFileAppender.
 
כמובן שכשעובדים עם windows azure אי אפשר לעבוד בצורה כזו, מכיוון שכדי לקרוא את הלוגים נצטרך להכנס למכונה ב - remote וגרוע מכך בדרך כלל יש יותר ממכונה אחת - מה שאומר שהלוגים נשמרים על מכונות שונות, וכמובן אחר restart שלהם לא יהיה זכר ללוגים.
 
 
לכאורה הפיתרון הפשוט הוא לשמור אותם לבסיס נתונים או ל - storage, הבעייה עם בסיס נתונים שהתימחור שלו הוא לפי גודל - ולוגים יכולים לתפוס הרבה מקום, מצד שני עבור storage משלמים לפי מספר טרנזקציות - ואם כותבים הרבה ללוג זה יכול לצאת הרבה כסף.
 
לכן חשבתי על הפיתרון הבא:
1. נכתוב את הלוג לבסיס נתונים.
2. פעם ביום נעביר את המידע משם ל - storage, כך נוודא שבסיס הנתונים לא גודל ולא כותבים ל - storage יותר מפעם ביום,
3. כדי לא להעמיס יותר מידי על בסיס הנתונים, נשמור את הלוגים לזיכרון, ובתהליך נפרד אחת לדקה נעביר את כל הלוגים לבסיס הנתונים.
 
 
את הפיתרון והקוד המלא (הדוגמא בבלוג לא מכילה את הקוד המלא) ניתן להוריד כאן.
(הדוגמא מכילה גם קוד sql ליצירת בסיס נתונים, לא לשכוח לשנות בקונפיגים את ההפנייה לבסיס הנתונים)
 
 
הקוד יראה כך:
 
 
ראשית נבנה בסיס נתונים שנראה כך:
 
log object
 
 
נייצר פרוייקט אשר נקרא לו Logging, הוא יהיה הפרוייקט היחידי אשר יחזיק reference ל - log4net.dll, שאר הפרוייקטים רק יצטרכו לכתוב בקונפיג את הגדרות ה - logging.
 
(בדוגמא השימוש הוא בעזרת entity framework 4.0)
 
אחרי שתייבאו את בסיס הנתונים למודל, נרצה לכתוב קוד שיודע להמיר בין אובייקט של log4net לבין אובייקט Log של המודל, נכתוב את הקוד הבא
 

public partial class Log

{

    public override string ToString()

    {

        return string.Format("{0} about {1} on {2} at {3}\r\n{4}\r\n{5}\r\n\r\n",

            this.Level,

            this.Project,

            this.Instance,

            this.Date,

            this.Message,

            this.Exception);

    }

 

    public static implicit operator Log(LoggingEvent le)

    {

        return new Log()

        {

            Date = le.TimeStamp,

            Exception =  le.GetExceptionString(),

            Message = le.MessageObject != null ? le.MessageObject.ToString() : "",

            Instance = 0,

            Level = le.Level.Name,

            Project = AssemblyName

        };

    }

 

    private static string _name;

 

    private static string AssemblyName

    {

        get

        {

            if (_name == null)

            {

                _name = Assembly.GetExecutingAssembly().GetName().Name;

            }

 

            return _name;

        }

    }

}

 
נשתמש במנגנון partial calss כדי להוסיף יכולות למודל שלנו.
נדרוס את מתודת ToString כדי לקבל מחרוזת "אנושית" עבור כל שגיאה,
לאחר מכן נשתמש במנגון implicit casting כדי להמיר בין אובייקט של LoggingEvent (ששיך ל - log4net) לבין אובייקט Log (ששיך למודל).
 
 
כעת נוסיף מחלקה בשם Logger שיהיו לה שני תפקידים, ראשית - היא תעשה עטיפה לכל הפונקציות של log4net, כדי שניתן יהיה לכתוב ללוג (לא מופיע בדוגמא רק בקוד להורדה),
שנית - היא תחזיק timer אשר בכל פעימה תיקח את כל הנתונים מהזיכרון (כזכור אנחנו משתמשים ב - log4net ושומרים את המידע בזיכרון) ותשפוך אותם לבסיס הנתונים,
הקוד יראה כך:
 

public static class Logger

{

    internal static readonly ILog _log;

    private static Timer _timer;

 

    public static int Period

    {

        get

        {

            int period;

 

            if (int.TryParse(ConfigurationManager.AppSettings["LogPeriod"], out period))

            {

                return period;

            }

 

            return 60000;

        }

    }

 

    static Logger()

    {

        XmlConfigurator.Configure();

        _log = LogManager.GetLogger("");

 

        _timer = new Timer(TimerCallback, null, 0, Period);

    }

 

    private static object lookObject = new object();

    private static void TimerCallback(object state)

    {

        try

        {

            lock (lookObject)

            {

                using (LogEntities context = new LogEntities("Logger"))

                {

                    MemoryAppender ap = (MemoryAppender)LogManager.GetLoggerRepository().GetAppenders()[0];

                    var logs = ap.GetEvents();

                    ap.Clear();

 

                    foreach (var item in logs)

                    {

                        context.Logs.AddObject(item);

                    }

 

                    context.SaveChanges();

                }

            }

        }

        catch (Exception ex)

        {

 

        }

    }

 

}

 

 
 
ב - static ctor נאתחל את הלוג כפי שיוגדר בקבצי הקונפיגורציה (נראה בהמשך).
 
המאפיין Period יאותחל כברירת מחדל על דקה - או בהגדרה בקונפיג. (מגדיר כל כמה זמן לאסוף את הנתונים מהזיכרון ולשמור בבסיס הנתונים).
 
וכמובן הפוקנציה של ה - timer, אוספת את הנתונים מה - Logger ושומרת אותם לבסיס הנתונים, (כל אובייקט מתוך GetEvents יהיה מסוג LoggingEvent, ובקריאה ל - AddObject הוא עובר המרה אוטומטית לאובייקט מסוג Log בעזרת הפונקציה מהמחלקה הקודמת שמשתמשת ב - Implicit casting.
 
 
כל מי שירצה לכתוב ללוג, יוסיף reference לפרוייקט זה, ויוכל לקרוא לפונקציות הכתיבה בלוג, הדבר היחיד שצריך לעשות זה להוסיף בקבצי הקונפיגורציה את הקוד הבא:
 

<configSections>

  <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />

</configSections>

<log4net>

  <appender name="MemoryAppender" type="log4net.Appender.MemoryAppender">

  </appender>

  <root>

    <level value="DEBUG" />

    <appender-ref ref="MemoryAppender" />

  </root>

</log4net>

 
 
הדבר האחרון שנשאר - הוא לוודא שפעם ביום נעביר את הלוגים מבסיס הנתונים ל - storage.
 
נייצר פרוייקט מסוג worker role ונכתוב את הקוד הבא:
 
 

public override void Run()

{

    while (true)

    {

        InitBlob();

        Thread.Sleep(86400000);

 

        using (LogEntities context = new LogEntities("Logger"))

        {

            var list = context.Logs.ToList();

            string str = "";

            foreach (var item in list)

            {

                str += (item.ToString());

                context.Logs.DeleteObject(item);

            }

 

            CloudBlob blob = _blobContainer.GetBlobReference(DateTime.Now.ToString("dd-MM-yyyy HH-mm"));

            blob.UploadText(str);

 

            context.SaveChanges();

        }

 

    }

}

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

Web Service - basic

 

בתפוז עלתה השאלה כיצד מתחילים לעבוד עם web services, בפוסט זה אני אדגים את השלבים צעד אחר צעד לאלו המתחילים את דרכם בעולם ה - web.
 
 
בפרוייקט ה - web צריך להוסיף item חדש מסוג web service (סיומת asmx) - נקרא לו MyWebService.
 
יווצרו שני קבצים - הראשון MyWebService.asmx שאם תלחצו עליו עם העכבר ותבחרו ב - View Markup תראו שיש בו את השורה הבאה בלבד
 

<%@ WebService Language="C#" CodeBehind="MyWebService.asmx.cs" Class="WebApplication10.MyWebService" %>

 

שכל מה שכתוב כאן - היכן נמצא הקוד של ה - WebService.
 
הקובץ השני נקרא MyWebService.asmx.cs המכיל את הקוד שלנו.
כברירת מחדל נקבל את הקוד הבא:
 

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

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

[System.ComponentModel.ToolboxItem(false)]

// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.

// [System.Web.Script.Services.ScriptService]

public class MyWebService : System.Web.Services.WebService

{

 

    [WebMethod]

    public string HelloWorld()

    {

        return "Hello World";

    }

}

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

public int Add(int a, int b)

{

    return a + b;

}

 
 
סביר להניח שיהיה לחצן כלשהו שיקרא למתודה זו - כל התיאור הנ"ל מתבצע על המחשב שעליו האפליקצייה רצה,
לפעמים נרצה לכתוב אפלקיצה מסוג לקוח-שרת, כלומר הלקוח יראה מסך ולחצנים ומדי פעם כשנצטרך לחשב משהו נפנה למחשב אחר (השרת) שיעשה עבורנו את החישוב.
כמובן שלא עבור כל דבר נרצה לפנות לשרת וחישובים שאפשר לעשות אצל הלקוח נעשה אצלו כדי לחסוך את הפנייה לשרת,
אך ישנם דברים שחייבים לעשות בצד השרת (כמו בדיקת שם משתמש וסיסמא).
 
אתרי אינטרנט מטבעם מתנהגים בצורה זו, אך web services מספקים את היכולת להפעיל אותם מרחוק גם עבור אפליקציות שאינם אתרי web.
 
כדי לעשות זאת נצטרך לבצע את התהליך שאיתו התחלנו את הפוסט, ונשים לב שיש למחלקה שלנו attribute בשם WebService.
 
הדבר החשוב הבא הוא - כל מתודה שנצרה להפעיל אותה מרחוק נוסיף עליה את ה - WebMethod, לדוגמא:
 

[WebMethod]

public int Add(int a, int b)

{

    return a + b;

}

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

[WebMethod(EnableSession = true)]

public int Add(int a, int b)

{

    HttpContext.Current.Session["key"] = "value";

    return a + b;

}

 
אני לא ארחיב בפוסט זה על תפקיד ה - Session, אך אציין שבמידה ואתם צריכים לשמור או לקרוא מידע בצד השרת ותרצו להשתמש ב - Session, צריך להוסיף את ההגדרה הזו.
 
(כדי לאפשר שימוש ב - asp.net ajax (כלומר - קריאה למתודות מקבצי javascript באמצעות ScriptManager) צריך להוריד את ההערה מ - ScriptService - למידע נוסף ומפורט בנושא, ניתן לקרוא במדריך השלם ל - ajax)
 
 
כעת נעבור לצד השני (כלומר מי שהולך להפעיל את המתודה מרחוק)
 
בפרוייקט אחר (לא חייב להיות באותו Solution) נלחץ לחיצה ימנית על הפרוייקט ונבחר ב - Add Service Reference.
במסך שיפתח לכם תוכלו להקליד את כתובת ה - web service או ללחוץ על Discover במידה ופרוייקט ה - WebService נמצא באותו Solution.
 
תקבלו את המסך הבא:
 
Service Reference
 
 
במידה ותלחצו על OK - יווצר לכם בצד הלקוח Proxy שעובד בטכנולוגיות WCF (לפירוט על הטכנולגיה)
 
כעת תוכלו לכתוב את הקוד הבא:
 

static void Main(string[] args)

{

    ServiceReference1.MyWebServiceSoapClient client = new ServiceReference1.MyWebServiceSoapClient();

    int res = client.Add(1, 2);

    Console.WriteLine(res);

}

 
 
כשבפועל הקריאה ל - Add תפנה לשרת ולא תבוצע על המחשב שלכם. (כמובן שבזמן הפיתוח אתם בדרך כלל גם הלקוח וגם השרת).
 
אחד הדברים הנחמדים שניתן לבצע הוא לחיצה על Advanced ולסמן את Generate asynchronous operations - ואז ניתן להפעיל כל מתודה בצורה אסינכרונית, לדוגמא:
 

static void Main(string[] args)

{

    ServiceReference1.MyWebServiceSoapClient client = new ServiceReference1.MyWebServiceSoapClient();

    client.AddCompleted += client_AddCompleted;

    client.AddAsync(1, 3);

 

    Console.ReadLine();

}

 

static void client_AddCompleted(object sender, ServiceReference1.AddCompletedEventArgs e)

{

    Console.WriteLine(e.Result);

}

 
 
במידה ונרצה ליצור אצל הלקוח שימוש ב - WebService (כמו פעם) ולא להשתמש ב - WCF, נוכל ללחוץ בתוך Advanced על Add Web Reference, נקבל את המסך הבא:
 
Web Reference
 
 
כעת ניתן להכניס את הכתובת או ללחוץ על Web Service in this solution
 
לאחר אישור ולחיצה על Add Reference - יווצר proxy שיעבוד בפרוטוקול SOAP, וניתן יהיה לכתוב קוד כזה:
 

localhost.MyWebService service = new localhost.MyWebService();

Console.WriteLine(service.Add(1, 3));

 
 
יש עוד מה להרחיב על הנושא, אך תיאור זה הינו מספק כדי להתחיל לעבוד עם Web Services

Exception handling in application

 

טיפול נכון בשגיאות הוא אחד הדברים המאתגרים בפיתוח אפליקציות, לדעתי אחד הדברים השגויים לעשות זה לכתוב בכל קטע קוד try, catch בלי לעשות כלום בקטע ה - catch רק כדי לבלום את התרסקות האפליקצייה.
 
הסיבה שהגישה הזו לא נכונה לדעתי, היא ש"טיפול" מסוג זה (כלומר לא לטפל רק להתעלם) יגרום בהכרח להתרסקות האפליקצייה במקום אחר מכיוון שאם קוד מסויים התרסק ולא עשה את העבודה כמו שצריך סביר להניח שקוד אחר המבוסס על הקוד שהתרסק לא יעבוד כמו שצריך, ובסוף התהליך כשנתרסק לא נדע את הסיבה האמיתית להתרסקות האפליקציה, (אם יצא לכם לדבג קוד שבכל מתודה יש בלוק try catch שלא עושה כלום, אתם כנראה מזדהים עם הכתיבה)
 
 
למעשה במידה והתרחשה שגיאה - ואני לא יודע כיצד לטפל בה אני מעוניין שהאפליקצייה תסגר (בצורה נעימה) כדי שאדע מה הייתה השגיאה.
 
 
הדרך הנכונה לטפל בשגיאות היא כך:
במידה ויודעים כיצד לטפל בה (למשל קבלת שם קובץ מהמשתמש) - לעטוף אותה בבלוק טיפול בשגיאות (לנסות לקבל שם קובץ חדש מהמשתמש),
במידה ורוצים להתעלם מהשגיאה (קורה לפעמים) לכתוב בלוק טיפול בשגיאות (ולכתוב ללוג את פרטי השגיאה),
בשאר המקרים יש לכתוב ללוג את הפרטים ולהציג הודעה מתאימה למשתמש ולפעמים אפילו לסגור את האפליקציה בצורה נכונה (לשמור מידע וכו').
 
 
כדי לחסוך את הצורך של כתיבת בלוק טיפול בשגיאות בכל מקום באפליקציה יש מקום מרכזי בו אפשר לכתוב קוד כללי של טיפול בשגיאות.
 
באפליקציות web יש את application_error ב - global.asax,
קוד מינימלי יהיה בסגנון הבא:
 

protected void Application_Error(object sender, EventArgs e)

{

    Exception ex = Server.GetLastError();

    Server.ClearError();

 

    // Write exception to log

    // redirect to error page

}

 
לולי הקריאה ל - ClearError האפליקציה תתרסק (עם הדף הצהוב הידוע) ולא ניתן יהיה להתעלם מהשגיאה או להפנות לדף שגיאה כללי.
 
 
באפליקציות windows forms ניתן לכתוב ב - Main את הקוד הבא:
 

Application.ThreadException += Application_ThreadException;

AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

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

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)

{

    Show((Exception)e.ExceptionObject);

}

 

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)

{

    Show(e.Exception);

}

 

internal static void Show(Exception e)

{

    ExceptionMessageBox emb = new ExceptionMessageBox(e);

    emb.Caption = "Error";

    emb.Symbol = ExceptionMessageBoxSymbol.Error;

    emb.Show(null);

}

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