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

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

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

February 2011 - Posts

אהבו את ההרצאה שלי ב - SDP 2011

 

למי שיוודע ולמי שעדיין לא - סלע מקיימת את כנס ה - SDP השנתי, ואני הולך להרצות שם על IE9 ו - HTML5.
 
 
ולמה אני מספר לכם ? ראשית כדי שתלחצו על הלינק למעלה ותרשמו ותגיעו לכנס (כדאי לכם).
 
 
אבל גם כדי שתכנסו לכאן ותעשו LIKE להרצאה שלי (אחרי שתעשו LIKE לדף של מכללת סלע)
 
 
תודה רבה מראש
ונתראה בכנס
Posted: Feb 09 2011, 10:17 PM by Shlomo | with 2 comment(s)
תגים:,

WCF 4 (Windows Communication Foundation) for Beginner - part 13

 

ללמוד WCF 4 פרק 13 - נקודות על אירוח WCF ב - IIS
 
(למעשה פרק זה היה צריך לבוא מיד אחרי פרק 6 (המדבר על אירוח) אבל לצערי לא הכרתי עדיין את הנושא עד שנתקלתי בו בעבודתי והגעתי  למאמר הבא ב - MSDN שעזר לי להבין דברים קצת יותר טוב)
 
 
נושאים בפרק 13.
הסבר על אירוח שירות ב - IIS.
לאפשר לגשת מ - WCF Service ל - HttpContext.
לאפשר לגשת מ - WCF Service ל - Session.
 
 
הסבר על אירוח שירות ב - IIS.
דברנו בפרק 6 על אירוח של שירותים בפלטפורמות שונות כמו Console Application, Windows Service ועוד, ראינו שהאירוח הטוב ביותר הוא ב - IIS, מכיוון שהוא מכיל כבר מנגנונים להתאוששות משגיאות, סגירה של השירות כשאף אחד לא משתמש בזה, מעקב אחרי ביצועים ועוד, כמו כן ראינו שהחל מ - Windows Vista ומעלה ניתן לארח ב - IIS גם bindings שאינם http וזאת בעזרת ההגדרה Enabled Protocols בהגדרות של האפליקצייה.
 
ישנם כמה הבדלים בין שירותי web רגילים לבין שירותי wcf המתארחים ב - IIS.
ההגדרות שאנו מגדירים בקונפיג במקטע ה - Authentication.
גישה למידע שנשמר ב - session.
גישה למידע שנשמר ב - cache.
גישה ל - HttpContex.Current.
גישה למידע ב - AppSettings.
 
בשירותי WCF המתארחים ב - IIS כברירת מחדל אין גישה לכל המידע שהזכרתי מקודם, מכיוון שהתהליך (שנקרא mixed mode) עובד בצורה הבאה:
מודול מיוחד שרשום ב - machine.config תופס את הקריאות של wcf services ב - BeginRequest (שזה השלב המוקדם ביותר) הוא מגדיר את המאפיין HttpContext.Current ל - null, ומכיוון שהמודול תפס את ה - request כל שאר התכונות (אילו שהזכרתי ועוד כמה) לא פעילים.
 
כדי לפתור את הבעייה ניתן להגדיר שהשירות יעבוד ב - Compatibility Mode (בהמשך נראה כיצד) במקרה כזה השירות עובד באותה צורה של Web Services, אבל מכיוון שזה עובר את כל ה - Life Cycle של ה - web אפשר להעביר רק בפרוטוקול http.
 
 
לאפשר לגשת מ - WCF Service ל - HttpContext.
כדי לבצע מה שהגדרנו מקודם צריך לעשות שני דברים, אחד להגדיר בקונפיג את השורה הבאה (ב - system.ServiceMode)
 

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />

 
 
ובנוסף צריך להגדיר במימוש השירות את הדבר הבא:
 

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]

public class Calc : ICalc

 
צירוף שני הדברים ייתן את האופצייה לגשת ל - HttpContext.Current ויפתור את רוב הבעיות שציינתי מקודם.
 
 
לאפשר לגשת מ - WCF Service ל - Session.
כדי לאפשר לגשת למידע שיש ב - session הלקוח צריך לאפשר cookies (מכיוון שהזיהוי של ה - session מתבצע בעזרת cookie) ולכן צריך להגדיר ב binding (אצל הלקוח) שמותר להשתמש ב - cookie.
 

<system.serviceModel>

  <client>

    <endpoint name="calcEndpoint"

              address="http://localhost/IISHost/Service1.svc"

              binding="basicHttpBinding"

              contract="Contarcts.ICalc"/>

  </client>

  <bindings>

    <basicHttpBinding>

      <binding allowCookies="true">

      </binding>

    </basicHttpBinding>

  </bindings>

</system.serviceModel>

 
אין חשיבות להגדיר את זה אצל השרת רק אצל הלקוח שכמובן יכול להיות כל אפליקצייה (אפילו Console)
 
אני מאוד ממליץ לקרוא את המאמר שפוסט זה מתבסס עליו, וגם כדאי לקרוא את זה.
Posted: Feb 09 2011, 06:12 PM by Shlomo | with 3 comment(s)
תגים:,

Print Lishner using PrintSystemJobInfo

בהמשך לפוסט בו תיארתי כיצד התחלתי לכתוב פתרון לאפשר לשנות את הגדרות ההדפסה (מחד צדדי לדו צדדי) לאחר שההדפסה נשלחה כבר למדפסת - הפיתרון התחיל מכך שכתבתי עטיפה ל - WMI שחושפת event בזמן שהדפסה נשלחת.
 
כאן נראה כיצד ניתן בעזרת מחלקה בשם PrintSystemJobInfo לעשות כמה דברים מעניינים. (System.Printing)
 
 
ראשית חשוב לדעת שבעזרת המחלקה אי אפשר לשנות הגדרות להדפסה קיימת - אפשר כן לשנות את הגדרות המדפסת עבור הדפסות עתידיות או לייצר הדפסות מקוד.
 
דרך אגב - למחלקה יש מאפיין JobStream ובכל מקום ברשת תמצאו את השאלה למה תמיד המאפיין הזה null, לא ראיתי בשום מקום תשובה אבל בעזרת קצת חפירה ב - reflector התברר לי שהמאפיין הזה משמש כשאנחנו יוצרים הדפסות מקוד ולא כשאנחנו משתמשים במחלקה הזו כדי לקבל מידע על הדפסה שנשלחה למדפסת בצורה רגילה, במקרה זה המאפיין הזה לא יחזיק שום ערך.
 
 
 
נרצה לכתוב מחלקה שתדע לקבל כפרמטר שם מדפסת ו - jobdId (שנקבל אותו מה - WNI) ותחזיר מופע של PrintSystemJobInfo.
 
 

public class Printer

{

    private PrintSystemJobInfo _systemJobinfo;

    private int _jobId;

 

    private const string DEFAULT_PRINT_QUEUE = "default";

 

    public Printer(int jobId, string printerName = DEFAULT_PRINT_QUEUE)

    {

        _jobId = jobId;

        PrintQueue queue;

 

        if (printerName == DEFAULT_PRINT_QUEUE)

        {

            queue = LocalPrintServer.GetDefaultPrintQueue();

        }

        else

        {

            PrintServer server = new PrintServer();

            queue = server.GetPrintQueue(printerName);

            if (queue == null)

            {

                throw new ArgumentException("printerName: " + printerName);

            }

        }

 

        _systemJobinfo = queue.GetJob(_jobId);

    }

 

    public void Pause()

    {

        _systemJobinfo.Pause();

    }

 

    public void Resume()

    {

        _systemJobinfo.Resume();

    }

}

 
 
כעת נוכל מכל מקום להירשם לאירוע ב - WMI ולפנות למלחקה Printer כדי לעצור את ההדפסה, למשל:
 

static void Main(string[] args)

{

    WMI.JubSent += WMI_JubSent;

    WMI.Enable = true;

 

    Console.ReadLine();

}

 

static void WMI_JubSent(object sender, GenericEventArgs<int> e)

{

    Printer printer = new Printer(e.Data);

    printer.Pause();

}

 
 
בפוסט הבא בנושא נמשיך להתקדם עד שנראה את הפתרון השלם לבעייה - כיצד לשנות הגדרות הדפסה למסמך שכבר נשלח למדפסת.
Posted: Feb 07 2011, 03:13 PM by Shlomo | with 1 comment(s)
תגים:,

WCF 4 (Windows Communication Foundation) for Beginner - part 12

 

ללמוד WCF 4 פרק 12 - Duplex Pattern
 
 
נושאים בפרק 12:
סיכום הפרק הקודם.
מה זה Duplex.
חסרונות ויתרונות בעבודה עם Duplex.
דוגמת קוד.
 
 
 
סיכום הפרק הקודם:
בפרק הקודם דברנו על סוגי התבניות שאנו עובדים איתם ב - WCF, הפשוט שמוגדר כברירת מחדל מזכיר את עולם ה - WEB, ולמעשה אנחנו עובדים ב - Request-Response כך שהלקוח פונה לשרת מבקש בקשה, הבקשה מתבצעת בשרת הלקוח מחכה לתשובה, מתי שהוא גם מקבל אותה וממשיך לעבוד.
 
בנוסף גם הכרנו את האופצייה להוסיף למתודות את IsOneWay כך שהלקוח לא מחכה לתשובה ורק מוודא שהבקשה הגיעה בהצלחה לשרת.
 
אופצייה אחרונה שהזכרנו היא עבודה ב - Stream וראינו כמה דוגמאות קוד (הורדת קבצים העלאת קבצים ועוד).
 
מעבר לכך הזכרנו את המושג Duplex ובפרק זה נרחיב את הנושא.
 
 
מה זה Duplex.
עבודה ב - Duplex מגדירה למעשה ש(כאילו) גם הלקוח הוא שרת וגם השרת הוא לקוח, כשלמעשה כשהלקוח פונה לשרת (בדרך כלל) הוא לא מחכה לתשובה ומתי שהוא השרת מחליט שהוא רוצה לענות ללקוח אזי השרת פונה מיוזמתו ללקוח ומפעיל אצלו מתודה ולמעשה השרת מתנהג כאילו הוא לקוח והלקוח מתנהג כשרת.
 
דרך אחרת להסתכל על זה - אפשר לומר שמדובר ב - Callback כמו שאנחנו מכירים מ - Delegate או בעבודה עם ajax, שבו אנחנו מפעילים מתודה ושולחים מתודה אחרת להפעלה, כך גם כאן הלקוח יפעיל את השרת וישלח לו מתודה להפעלה בזמן שהשרת יחליט שזה זמן טוב.
 
 
חסרונות ויתרונות בעבודה עם Duplex.
היתרון הגדול של עבודה ב - Duplex היא כמובן ביצועים מכיוון שהלקוח לא מחכה לתשובה אלא יקבל מתי שהוא את התשובה (או כמה תשובות בבת אחת), החסרונות לעומת זאת הם גם לא קטנים, היות שהשרת צריך לפנות ללקוח זה אומר שהוא צריך את היכולת לתקשר איתו כלומר (וכאן בדרך כלל הנפילה) חומת האש צריכה לאפשר את התקשורת, כמו כן אנחנו צריכים לעבוד עם binding שמאפשר עבודה עם קריאה משני הצדדים.
 
 
דוגמת קוד.
 
בעבר כתבתי דוגמת קוד שמדמה messenger בעזרת duplex, היום אני אדגים שרת ש(כאילו) מספק מידע אודות מניות, לקוחות יכולים להרשם כדי לקבל מידע על מניות ספציפיות.
 
(אני לא אציג כאן את כל הקוד, להורדת הקוד המלא - הקוד לא כתוב בצורה הכי נכונה אלא מכוון להציג בפשטות עבודה עם Duplex)
 
נתחיל כרגיל ב - Contracts
 

namespace Contracts

{

    [ServiceContract(CallbackContract = typeof(IStockClient))]

    public interface IStockService

    {

        [OperationContract(IsOneWay = true)]

        void Regisetr(string stockName);

    }

}

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

namespace Contracts

{

    [ServiceContract]

    public interface IStockClient

    {

        [OperationContract(IsOneWay = true)]

        void GetResult(IEnumerable<StockResponce> results);

    }

}

 
אפשר לראות שהוא מוגדר ב - Contrat לכל דבר (האובייקט StockResponse הינו אובייקט המכיל מחרוזת ומחיר למנייה - תוכלו לראות אותה על ידי הורדת הקוד המלא)
 
ננתח את מה שראינו עד כה.
יש לנו שני Contracts אחד עבור השרת ואחד עבור הלקוח, השרת מכיר את ההגדרה של ה - Contract שהלקוח יממש.
 
כעת נראה את הקונפיג של ה - host
 

<system.serviceModel>

  <services>

    <service name="Service.StockService">

      <endpoint address="http://localhost:1245/Stock"

                binding="wsDualHttpBinding"

                contract="Contracts.IStockService"></endpoint>

    </service>

  </services>

</system.serviceModel>

 
אני משתמש ב - wsDualHttpBinding שזה אחד מאותם bindings המאפשרים עבודה ב - duplex.
 
 
כעת נראה את הלקוח:
 

static void Main(string[] args)

{

    var factory = new DuplexChannelFactory<IStockService>(ClientCallback.Instance, "stock");

    IStockService proxy = factory.CreateChannel();

 

    while (true)

    {

        Console.Write("Register: ");

        string stock = Console.ReadLine();

        proxy.Regisetr(stock);

    }

}

 
ראשית במקום שימוש ב - ChannelFactory השתמשנו ב - DuplexChannelFactory שמקבל מישהו שמממש את IStockClient (שזה אותו ממשק שהוגדר לשרת שהלקוח יממש - מיד נראה אותו.
 
לארח מכן יש עבודה רגילה של אפשרות לרשום מניות שרוצים לקבל עליהם עדכון מהשרת.
 

public class ClientCallback : IStockClient

{

    #region Singelton

    // here is the singelton code...

    #endregion

 

    public void GetResult(IEnumerable<StockResponce> results)

    {

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

        Console.WriteLine("Result: " + DateTime.Now.ToLongTimeString());

 

        foreach (var item in results)

        {

            Console.WriteLine(item);

        }

 

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

    }

}

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

<system.serviceModel>

  <client>

    <endpoint name="stock"

              address="http://localhost:1245/Stock"

              binding="wsDualHttpBinding"

              contract="Contracts.IStockService"></endpoint>

  </client>

  <bindings>

    <wsDualHttpBinding>

      <binding clientBaseAddress="http://localhost:33/Client"></binding>

    </wsDualHttpBinding>

  </bindings>

</system.serviceModel>

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

public class StockService : IStockService

{

    private static Dictionary<IStockClient, List<string>> _list;

 

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

public void Regisetr(string stockName)

{

    var client = OperationContext.Current.GetCallbackChannel<IStockClient>();

    if (!_list.ContainsKey(client))

    {

        _list.Add(client, new List<string>());

    }

 

    if (!_list[client].Contains(stockName))

    {

        _list[client].Add(stockName);

    }

}

 
בשלב ראשון אנחנו מוצאים את ה - reference אל הלקוח ושומרים אותו ברשימה (אם הוא לא קיים) לאחר מכן אנחנו מוסיפים את המניה שהוא רצה לקבל עליה מידע.
 
כל מה שנשאר לנו לעשות זה מתי שהוא להחזיר תשובה ללקוחות (במימוש שלנו נשתמש ב - timer שירוץ כל 10 שניות ויגדיר מחירים רנדומליים לכל המניות)
 

 

private static Timer _timer;

 

static void _timer_Elapsed(object sender, ElapsedEventArgs e)

{

    var list = GetStocks();

    var res = SetPrice(list);

 

    foreach (var client in _list)

    {

        var x = from n in res

                where client.Value.Contains(n.Name)

                select n;

 

        client.Key.GetResult(x);

    }

}

 
המתודה מחלצת החוצה את כל המניות מגדירה מחיר רנדומלי.
יוצרים רשימה של כל המניות עבור כל לקוח ושולחים לו את המידע המבוקש.
 
אמנם הפוסט הזה הוא סיכום די פשוט על עבודה ב - Duplex אבל זה התחלה טובה להכיר את המושגים ולעבוד ב - Duplex.
 
 
Posted: Feb 06 2011, 07:43 PM by Shlomo | with 4 comment(s)
תגים:,

RSS and ATOM Reader in C#

 

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

Rss20FeedFormatter rss20 = new Rss20FeedFormatter();

XmlReader reader = XmlReader.Create("http://blogs.microsoft.co.il/blogs/shlomo/rss.aspx");

rss20.ReadFrom(reader);

 

 
 
או קוד כזה (ל - ATOM)
 
 

Atom10FeedFormatter atom = new Atom10FeedFormatter();

XmlReader reader = XmlReader.Create("http://blogs.microsoft.co.il/blogs/shlomo/atom.aspx");

atom.ReadFrom(reader);

 
 
 
וכמובן ניתן לקרוא את כל התוכן בצורה קלה מבלי צורך להתחיל לפרק את ה - xml או להשתמש בקוד חיצוני אחר.
Posted: Feb 05 2011, 09:43 PM by Shlomo | with no comments
תגים:, ,

WCF 4 (Windows Communication Foundation) for Beginner - part 11

ללמוד WCF 4 פרק 11 - Messaging Patterns
 
נושאים בפרק 11:
מה זה Messaging Patterns.
המשמעות של Request-Respone Pattern.
המשמעות של One-Way Pattern, ודוגמת קוד.
המשמעות של Duplex Pattern.
המשמעות של Streaming Pattern, והרבה קוד.
בפרק הבא.

 
מה זה Messaging Patterns.
אנחנו כבר יודעים כיצד (מעשית) ניתן לשלוח הודעות לשירות ולקבל תשובה, בדרך כלל אנחנו מתחילים לכתוב את ה - Contract ולפי זה אנחנו מממשים את השירות, לפעמים הצורה שבה התקשורת מתבצעת צריכה גם מחשבה מעבר לעובדה שנגדיר את ה - Binding.
הרבה פעמים צריך גם לדעת כיצד להגדיר את ה - Contract (בעזרת attributes) כדי לשלוח את ההודעות ולקבל תשובות בצורה שהכי מתאימה לנו לפי סוג השירות.
 
שאלות לדוגמא שאנחנו צריכים לשאול לפני שנתחיל לממש את השירות.
האם הלקוח צריך את התשובה מן השירות מיידית או שאולי הוא יכול לקבל את התשובה מאוחר יותר (על ידי פנייה מיוחדת לשירות כדי לקבל תשובה על פנייה קודמת או בדוא"ל).
 
האם הלקוח צריך לקבל תשובה מיוחדת לכל פנייה או שאולי הוא יכול לקבל כמה תשובות בבת אחת על כמה פניות קודמות (למשל שערי מניות).
 
האם הלקוח צריך לדעת שהבקשה טופלה בהצלחה בשרת ללא שגיאות או שהוא יכול להסתפק בלדעת שהבקשה הגיעה לשרת בהצלחה.
 
מה גודל המידע שמעבירים או מקבלים מהשרת.
 
 
התשובות לשאלות הללו למעשה יכריעו באיזה Mssaging Pattern נשתמש.
 
 
המשמעות של Request-Respone Pattern.
זוהי התבנית הרגילה שעבדנו איתה עד כה, למעשה זוהי התבנית שעובדים איתה לאורך השנים ברשת, הלקוח (תמיד הוא) יוזם פניה לשרת, מחכה לתשובה כלשהיא מהשרת (מחכה עד שהשרת יסיים להפעיל את המתודה - גם אם לא מקבלים שום ערך בחזרה), לאחר מכן מקבלים (או שלא) תשובה מהשרת וממשיכים לעבוד כרגיל.
 
במידה ולא נגדיר אחרת זוהי התנהגות ברירת המחדל לשליחת הודעות.
 
 
המשמעות של One-Way Pattern, ודוגמת קוד.

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

[OperationContract(IsOneWay = true)]

void SaveChanges(Person person);

 
 
המשמעות של Duplex Pattern.
הלקוח פונה לשרת איך שהוא רוצה (כלומר ב - Request-Response או OneWay) השרת יבצע את הפעולות שלו ומתי שהוא השרת יזום פנייה ללקוח כדי להחזיר לו תשובה, במילים אחרות הלקוח נהפך במקרה הזה לשרת בעצמו ואפשר ליזום אליו פניות.
 
אני לא אציג כאן דוגמת קוד מכיוון שהונושא שווה פוסט בפני עצמו.
 
 
המשמעות של Streaming Pattern, והרבה קוד.
 
לפעמים נרצה שמתודה מסויימת תקבל או תחזיר stream, זה יכול להיות עבודה עם קבצים או עם הרבה מידע, במידה ולא נעבוד עם stream נוכל לקבל OutOfMemory אם לא יהיה מספיק מקום ובנוסף הלקוח מחכה לקבלת כל המידע, לעומת זאת בעבודה עם stream אפשר להתחיל לנתח את המידע שהתקבל מבלי לחכות לכולו.
מתודות שעובדות עם stream לא יכולות לקבל עוד פרמטרים מעבר לפרמטר ה - stream, נוסף על כך מתודה המקבל stream אינה יכולה להחזיר stream.
נצטרך לקנפג את ה - TranferMode ל - Stream, RequestStream, ResponseStream בהתאם למתודה.
בנוסף נצטרך להגדיר את המאפיין maxReceivedMessageSize שמגדיר מה המקסימום ב - bytes שניתן להעביר על גבי השירות, יש חשיבות רבה למאפיין הזה, אף על פי שלכאורה המידע לא נשלח בבת אחת עדיין נצטרך להגדיר מה המקסימום בבתים שמתודה יכולה לקבל או להחזיר ובמידה וה - stream יהיה יותר גדול, הלקוח יתרסק, המקסימום שאפשר להגדיר הוא: Int64.MaxValue
דוגמת קוד לנגן אודיו של קבצי wav.
הגדרת ה - Contract
 

namespace Contarcts

{

    [ServiceContract]

    public interface IPlay

    {

        [OperationContract]

        Stream GetSong(string name);

    }

}

 
מימוש השירות:
 

namespace Service

{

    public class Play : IPlay

    {

        public Stream GetSong(string name)

        {

            FileStream stream = File.OpenRead(@"C:\tmp\" + name + ".wav");

            return stream;

        }

    }

}

 
 
ההגדרה בקונפיג של ה - host
 

<system.serviceModel>

  <services>

    <service name="Service.Play">

      <endpoint address="http://localhost:3333/play"

                binding="basicHttpBinding"

                bindingConfiguration="httpStream"

                contract="Contarcts.IPlay">

      </endpoint>

    </service>

  </services>

  <bindings>

    <basicHttpBinding>

      <binding name="httpStream"

                maxReceivedMessageSize="2147483647"

                transferMode="StreamedResponse">

      </binding>

    </basicHttpBinding>

  </bindings>

</system.serviceModel>

 
במידה ו - host הינו IIS ואתם שולחים stream כפרמטר ייתכן שתצטרכו גם להגדיר את זה:
 

<system.web>

  <httpRuntime maxRequestLength="2147483647"/>

</system.web>

 
 
צד הלקוח (אני משתמש במקרה הזה ב - ChannelFactory)
 

static void Main(string[] args)

{

    ChannelFactory<IPlay> channel = new ChannelFactory<IPlay>("playEndpoint");

    IPlay proxy = channel.CreateChannel();

 

    using (Stream stream = proxy.GetSong(Console.ReadLine()))

    {

        SoundPlayer mp = new SoundPlayer(stream);

        mp.Play();

 

        Console.ReadLine();

    }

}

 
וכמובן הקונפיג של הלקוח:
 

<system.serviceModel>

  <client>

    <endpoint name="playEndpoint"

              address=http://localhost:3333/play

              binding="basicHttpBinding"

              contract="Contarcts.IPlay"/>

  </client>

  <bindings>

    <basicHttpBinding>

      <binding transferMode="StreamedResponse"

                maxReceivedMessageSize="2147483647">

      </binding>

    </basicHttpBinding>

  </bindings>

</system.serviceModel>

 
 
כדאי גם לשים לב למאפיין receiveTimeout ולהגדיל אותו (גם בשרת וגם בלקוח) מכיוון שקריאה של הרבה מידע יכולה לזרוק TimeoutException
 

receiveTimeout="00:10:00"

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

public Stream GetFile(string name)

{

    return File.Open(name, FileMode.Open);

}

 
הלקוח:
 

using (Stream stream = proxy.GetFile(Console.ReadLine()))

{

    byte[] bytes = new byte[1024];

    int count = 0;

    while ((count = stream.Read(bytes, 0, 1024)) > 0)

    {

        Console.WriteLine(count);

    }

 

    Console.ReadLine();

}

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

<binding name="httpStream"

          maxReceivedMessageSize="2147483647"

          transferMode="StreamedResponse">

  <readerQuotas maxBytesPerRead="2048" />

</binding>

 
בדוגמא הזאת הגבלנו את מספר הבתים המקסימלי לקריאה ל - 2048 בתים (ברירת המחדל היא 4096)
 
 
במידה ואנחנו צריכים לקבל stream ובנוסף עוד פרמטרים, נצטרך להגדיר את השירות כ - REST.
 
הגדרת ה - Contract:
 

namespace Contarcts

{

    [ServiceContract]

    public interface IFileWorker

    {

        [OperationContract]

        [WebGet]

        Stream DownloadFile(string fileName);

 

        [OperationContract]

        [WebInvoke(Method = "POST", UriTemplate = "UploadFile?fileName={fileName}")]

        void UploadFile(string fileName, Stream fileContents);

    }

}

 
המתודה הראשונה כמובן יכולה לעבוד גם בלי REST, אבל בכל מקרה אני אציג דוגמא לזה.
 
 
המימוש:
 

namespace Service

{

    public class FileWorker : IFileWorker

    {

        public Stream DownloadFile(string fileName)

        {

            WebOperationContext.Current.OutgoingResponse.Headers["Content-Disposition"] =

                    "attachment; filename=" + fileName;

 

            return File.Open(fileName, FileMode.Open);

        }

 

        public void UploadFile(string fileName, Stream fileContents)

        {

            using (FileStream fs = File.OpenWrite(fileName))

            {

                byte[] bytes = new byte[1024];

                int count = 0;

                while ((count = fileContents.Read(bytes, 0, 1024)) > 0)

                {

                    fs.Write(bytes, 0, count);

                }

            }

        }

    }

}

 
וכמובן קובץ הקונפיג ב - host.
 

<system.serviceModel>

  <services>

    <service name="Service.FileWorker" behaviorConfiguration="filemt">

      <endpoint binding="webHttpBinding" behaviorConfiguration="webHttp"

                address="http://localhost:3333/file"

                bindingConfiguration="httpAllStream"

                contract="Contarcts.IFileWorker"></endpoint>

    </service>

  </services>

  <bindings>

    <webHttpBinding>

      <binding name="httpAllStream"

                maxReceivedMessageSize="2147483647"

                receiveTimeout="00:10:00"

                transferMode="Streamed">

      </binding>

    </webHttpBinding>

  </bindings>

  <behaviors>

    <serviceBehaviors>

      <behavior name="filemt">

        <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:3333/filemt" />

        <serviceDebug includeExceptionDetailInFaults="true"/>

      </behavior>

    </serviceBehaviors>

    <endpointBehaviors>

      <behavior name="webHttp">

        <webHttp faultExceptionEnabled="true" />

      </behavior>

    </endpointBehaviors>

  </behaviors>

</system.serviceModel>

 
 
בהתחלה יש כמובן את ההגדרה של השירות וכמו שאמרנו מקודם זה חייב להיות REST ולכן ה - binding מוגדר כ - webHttpBinding.
יש כמובן את ההגדרות עבור ה - binding - מה שאנחנו מגדירים שזה יהיה stream.
כמובן שצריך לתת behavior מתאים.
 
אף על פי שזה יעבוד אם תגדירו serviceMetadata ותנסו לגלוש אליו הוא ייתן לכם הודעת שגיאה שאי אפשר יותר מפרמטר אחד (מוזר).
 
 
כעת למתודה DownloadFile ניתן פשוט לגלוש בצורה כזאת:
 
 
וניתן יהיה להפעיל את המתודה השנייה ב - POST (מקוד) ולהעלות קבצים לשרת.
 
 
בפרק הבא:
נראה כיצד מגדירים Duplex וכיצד השרת יוזם פניות ללקוח.
Posted: Feb 03 2011, 05:52 PM by Shlomo | with 3 comment(s)
תגים:, ,

Print Lishner using WMI

 

לאחרונה התבקשתי לכתוב אפליקצייה שעוצרת כל הדפסה שנשלחת למדפסת ושואלת את המשתמש האם הוא רוצה להדפיס דו צדדי, במידה וכן לשנות את הגדרות ההדפסה.
 
לכאורה זה נשמע ממש קל, אבל לאחר שמצאתי את הפתרון אני חייב להגיד שזה היה אחד מהפרוייקטים היותר מאתגרים שעשיתי, כמות המידע שלמדתי בזמן חיפוש אחר פתרון היא באמת רבה, ואני אנסה להעביר את החלקים המעניינים מתוך הפתרון המלא בכמה פוסטים כך שלא יהיה יותר מידי חומר בפוסט אחד.
 
בפוסט הזה אני אדגים את השלב הראשון בפתרון.
די ברור שעל ההתחלה צריך איכשהו להאזין לשליחת הדפסות ולאפשר לעצור אותם, ב - windows הדרך להאזין לבקשות הדפסה היא בעזרת שאילתת WMI (טכנולוגיה המאפשרת להגדיר שאילתות על מערכת ההפעלה ולקבל את המידע (החל ממידע על דיסק שחובר למחשב ועד לכשלים בתקשורת).
 
(את המחלקה המלאה ניתן להוריד מכאן)
 
נגדיר מחלקה סטטית בשם WMI.(צריך להוסיף reference ל - System.Management.dll)
 

public static class WMI

{

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

private static ManagementEventWatcher _printEventWatcher;

private static double? _queryInterval;

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

public static event EventHandler<GenericEventArgs<int>> JubSent;

 
(לקריאה על GenericEventArgs)
 
כעת נגדיר מאפיין שייאתחל את המשתנה queryInterval. (במידה ויש הגדרה בקונפיג נקח את הערך משם, אחרת נגדיר ברירת מחדל)
 

private static double QueryInterval

{

    get

    {

        if (_queryInterval == null)

        {

            double result;

            if (double.TryParse(ConfigurationManager.AppSettings["QueryInterval"], out result))

            {

                _queryInterval = result;

            }

            else

            {

                _queryInterval = 0.001;

            }

        }

 

        return _queryInterval.Value;

    }

}

 
 
נגדיר את השאילתא למערכת ההפעלה
 

private static string PrintJobQuery

{

    get

    {

        return "SELECT * FROM __InstanceCreationEvent WITHIN " +

                QueryInterval +

                " WHERE TargetInstance ISA \"Win32_PrintJob\"";

    }

}

 
 
 
כעת נגדיר static ctor עבור המחלקה שיאתחל את השדות:
 

static WMI()

{

    _printEventWatcher = new ManagementEventWatcher();

    _printEventWatcher.Query = new EventQuery(PrintJobQuery);

    _printEventWatcher.Scope = new ManagementScope("\\\\" + Dns.GetHostName() + "\\root\\CIMV2");

    _printEventWatcher.EventArrived += _printEventWatcher_EventArrived;

}

 
 
נגדיר מתודה שנפעיל בזמן שהדפסה מתקבלת:
 

private static void _printEventWatcher_EventArrived(object sender, EventArrivedEventArgs e)

{

    if (JubSent != null)

    {

        OnJobSent(e);

    }

}

 

private static void OnJobSent(EventArrivedEventArgs e)

{

    int jobId = int.Parse(GetValue(e, "JobId").ToString());

    var args = new GenericEventArgs<int>(jobId);

    JubSent.Invoke(null, args);

}

 

private static object GetValue(EventArrivedEventArgs e, string key)

{

    object val = ((ManagementBaseObject)e.NewEvent.Properties["TargetInstance"].Value).Properties[key].Value;

    return val;

}

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

private static bool _enable;

 

public static bool Enable

{

    get

    {

        return _enable;

    }

    set

    {

        if (value)

            _printEventWatcher.Start();

        else

            _printEventWatcher.Stop();

 

        _enable = value;

    }

}

 
 
באחד הפוסטים הבאים אני אספר כיצד ניתן לשנות את ההגדרות (חלק מהם) לאחר שההדפסה נשלחה ומה המשמעות של spool file וכיצד הוא בנוי.
Posted: Feb 02 2011, 10:43 PM by Shlomo | with 2 comment(s)
תגים:, ,

Working with Microsoft.mshtml in c#

 

ניתוח קוד html ב - #C באפליקציית Console Application

 
 
קבלתי משימה - נתנו לי כתובת דף אינטרנט המציג תוצאות חיפוש לפי פרמטרים, השאילתא מחזירה  כ - 86 דפים ובכל דף 10 תוצאות (סך הכול 860 תוצאות), מה שהייתי צריך לעשות זה:
1. לגלוש לאתר מקוד
2. לנתח את התוצאות
3. להוציא אותם לאקסל.
 
 לגלוש לאתר מקוד זה מאוד פשוט:
 

private string GetHtml(string url)

{

    WebClient client = new WebClient();

    string str = client.DownloadString(url);

    return str;

}

 
להוציא את התוצאות לאקסל, זה גם לא נורא (אני אראה את זה בפוסט עתידי)
 
הבעייה הייתה לנתח את התוצאות, אופצייה אחת היא לעשות מניפולציות על המחרוזת וזה די ברור שאני לא ממש רוצה לעשות את זה, האופצייה השנייה היא לעבוד עם Microsoft.mshtml שזה dll שאפשר לכתוב איתו (כמעט) דפדפן, למעשה הפקד web browser ב - win form משתמש מאחורי הקלעים עם ה - dll הזה,
 
נכתוב קוד כזה:
 
 

private HTMLDocumentClass GetDocument(string url)

{

    var docClass = new HTMLDocumentClass();

    IHTMLDocument2 iDoc = (IHTMLDocument2)docClass;

 

    object html = GetHtml(url);

    iDoc.write(html);

 

    return docClass;

}

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

var doc = GetDocument(GetUrl(1));

var header = (HTMLHeaderElementClass)doc.getElementById("h1ElementId");

 

string hedaerText = header.innerText;

 
 
Posted: Feb 01 2011, 11:35 AM by Shlomo | with 8 comment(s)
תגים:, ,