بدء العمل مع SignalR ( الجزء الأول )

للحصول على معلومة من شبكة الويب فقط افتح المتصفح و قم بإرسال ( Request ) للموقع الذي ترغب به و انتظر إجابة ( Response ) من الخادم، هكذا تعمل الويب من بداية التسعينات ، عندما كانت شبكة الويب محصورة على علماء الفيزياء حيث كانت الشبكة العنكبوتية ( كما تحب أن تترجمها الكتب العربية ) عبارة عن مجموعة كبيرة من الصفحات التي تحتوي على معلومات ثابته ترتبط ببعضها البعض عن طريق روابط ( Hyper Link )، طبعا تطورت الويب تطورا كبيرا خلال 24 عاما الماضية ، فبعد أن كانت تلك الصفحات ثابته أصبحنا نرى تدخل قواعد البيانات لتصبح تلك الصفحات Dynamic حيث يتم تحديث المعلومات بشكل مباشر بمجرد طلب العميل لها، كان ذلك كان الأمر رائعا للغاية إلى ان ظهرت بعض التطبيقات الحمقاء التي جلعت كل هذا العمل يصبح هباء منثورا، لنأخذ على سبيل المثال تطبيقات البورصة حتى وقت قريب كانت هذه التطبيقات محصورة فقط على تطبيقات سطح المكتب و إن كانت موجودة على الويب فكان عملية تحديثها مرهقة حيث يتطلب الامر من المستخدم ان يقوم بتحديث الصفحة بشكل يدوي إن لم يقم المطور بوضع مؤقت لتحديث الصفحة كل فترة معينة الامر الذي يستهلك الكثير من قدرات الجهاز و الاتصال، أيضا ما زلت أتذكر برامج الـ Chat التي كانت تعمل باستخدام الـ Java Applet و التي لن كانت لا تعمل على أنظمة الـ OSX لفترة طويلة فكان على مستخدمي تلك الأجهزة تثبيت Windows XP لدخول تلك البرامج ، لذلك كان علينا إعادة النظر في طريقة عمل الويب.

ظهرت فكرة مجنونة في منتصف عام 2000 و كان مصدرها بحث إيطالي يمكنك الاطلاع عليه على الرابط (docs.mc2labs.net ) ، اطلق على الفكرة اسم Real Time Web Application الفكرة كانت بسيطة للغاية لماذا يجب على العميل أن يطلب المعلومات من الخادم، ماذا إذا كان بمقدور الخادم عند توفر معلومات جديدة أن يقوم بإرسالها للعميل؟ لتوضيح الفكرة أكثر للنظر إلى الصورة التالية، التي توضح كيف يقوم تطبيق بورصة بالعمل حيث يقوم بطلب تحديث للبيانات كل فترة معينة و عندما يحصل على معلومات جديدة يقوم بعرضها على العميل، الصورة:
clip_image002
صورة توضيحية لفكرة عمل تطبيق البورصة سابقا

بعيدا عن المشاكل التي تواجه هذا الحل بدء من مشكلة التحديث وصولا إلى إنهاك الاتصال أضف إلى ذلك مشاكل محدودية الـ bandwidth للاستضافة التي من شأن حل المشكلة بهذه الطريقة ان يكون قاتلا لها، بعيدا عن كل هذا الحل غير المنطقي نهائيا، لنفترض أنه لا توجد بيانات جديدة خلال ساعة كاملة على الرغم من أنه تطبيق بورصة لأي سبب كان و بأن التطبيق يقوم بتواصل مع الخادم كل ثانية هذا يعني أن كل عميل سيقوم بطلب 3600 Request خلال الساعة بلا فائدة، و حتى ان كان هناك بيانات أنت أيضا عليك أن ترسل هذا الكم الهائل من الـ Request للخادم، الأن للنظر إلى الصورة التالية:
clip_image004
clip_image006

صورة توضيحية لطريقة عمل Real Time Web Application

حسنا هناك تغير جدري في طريقة الحوار بين العميل و الخادم في الصورة التي بالأعلى، يقوم العميل بفتح الاتصال بـ Server ثم يطلب من الخادم عند توفر معلومات جديدة أن يشعره بذلك، عندها يقوم العميل بجلب البيانات الجديدة، يحدث هذا الامر في كل مرة تتوفر بها معلومات جديدة حتى يقطع العميل الاتصال.

clip_image008

لحظ الفرق الكبير في الأداء الصورة في الأعلى تلاحظ حجم و كمية الطلبات المهولة لثلاثة عملاء فقط و في مثالنا البورصة التي لا توجد لها بيانات لمدة ساعة هذا يعني 10800 طلب ذهب هباء منبثا، في حين الصورة التي بالأسفل لدي ثلاثة اتصالات مع الخادم و عملية الـ Request لبيانات جديدة تحدث فقط عندما يكون هناك بيانات جديدة.

على الرغم من هذه الفكرة عظيمة إلى أنها لفترة طويلة ضلة حبيسة الادراج لا يهم لماذا الان، المهم أنه و مع تطوير الويب نحن نشاهد استخدام هذه التقنية الأن بكثافة في الـ Facebook عند تحديث الصفحة الرئيسية للمستخدم، في Twitter عند تحديث TimeLine في Gmail عند وصل بريد الكتروني جديد، في Outlook عند استخدامك للـ Messenger، في تطبيقات الأحوال الجوية ، أسعار العملات، أسعار المعادن و نفط، في خرائط Nokia لتحميل بيانات الازدحام المروري، في تطبيقات إدارة الطوارئ في المستشفيات، في تطبيقات المهتمة بالإحصائيات، في أنظمة مراقبة البنى التحتية التقنية مثل IBM guardium التي تراقب قواعد البيانات بتو و اللحظة، في التطبيقات التي تحتاج إلى مزامنة بين الخادم و بين العملاء و ما اكثرها الان في تطبيقات الهواتف الذكية، في كل هذه التطبيقات انت لا تحتاج إلى اغلاق التطبيق أو تحديث الصفحة أنت ترى إما رسالة او إشعار و حين تقوم بضغط عليه يقوم التطبيق بالحصول على المعلومات الجديدة.

كل هذا يقودنا إلى سؤال واحد : ما هو SignalR ؟ في البداية تستخدم فكرة Real Time Web Application العديد من التقنيات لتنفيذها على سبيل المثال و ليسل الحصر ، Web socket و Server side Event و Forever frame و Long poling و push today و غيرها من التقنيات التي تقوم بنفس العمل و لكن بطرق مختلفة، يأتي هنا SignalR حيث يستخدم تقنيتي الـ Web Socket و الـ Forever Frame من أجل تنفيذ فكرة Real Time Web Application هذا يعني الـ SignalR هو إطار عمل قامت Microsoft مؤخرا بتطويره لتفنيد فكرة Real Time Web Application داخل اطار العمل الـ .net .

من يجب ان يتهتم لأمر SignalR
مطوري الويب بدرجة الأولى و المهتمين بتطوير تطبيقات تعرض البيانات أو تخلق نوعا من التواصل بين المستخدمين مثل برامج الشبكة الاجتماعية أو كل مطور مازال حتى اللحظة يستخدم الـ web socket ، أيضا مطوري خدمات الويب الراغبين بتطوير خدمات ويب تقوم هيا بإرسال بيانات إلى كافة العملاء المتصلين بدون أن يرسل العميل أي طلب لها، المستخدمين المهتمين ببناء الألعاب باستخدام HTML 5 ستكون هذه التقنية بمثابة جنة الخلد بنسبة لكم، المطورين المهتمين ببناء برامج إحصائية تقوم بعرض البيانات مباشرة عند اضافتها لقاعدة البيانات مثل الإحصائيات الطبية في المستشفيات و البنوك و شعب إدارات المرور التي تتطلب أن ترى البيانات خلال أجزاء من الثانية.

على الرغم من SignalR لم يقدم في الحقيقة شيئاً جديدا فهذه التقنية ليست من بنات اختراع Microsoft لكن الشيء الجديد الذي يقدمه SignalR هو تبسيط استخدام هذه التقنيات للنظر إلى المقارنة التالية، لشفرة تقوم بعمل Chat باستخدام Web Socket هذا جزء من الشفرة:
public class WSHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
    if (context.IsWebSocketRequest)
    {
        context.AcceptWebSocketRequest(ProcessWSChat);
    }
}

public bool IsReusable { get { return false; } }
private async Task ProcessWSChat(AspNetWebSocketContext context)
{
    WebSocket socket = context.WebSocket;
    while (true)
    {
            ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[1024]);
            WebSocketReceiveResult result = await socket.ReceiveAsync(
            buffer، CancellationToken.None);
            if (socket.State == WebSocketState.Open)
            {
                string userMessage = Encoding.UTF8.GetString(
                buffer.Array، 0، result.Count);
                userMessage = "You sent: " + userMessage + " at " + 
                DateTime.Now.ToLongTimeString();
                buffer = new ArraySegment<byte>(
                Encoding.UTF8.GetBytes(userMessage));
                await socket.SendAsync(
               buffer، WebSocketMessageType.Text، true، CancellationToken.None);
            }
            else
            {
                break;
            }
        }
    }
}

الشفرة التي في الأعلى تقوم بإنشاء HTTP handler و التي تستقبل طلبات الـ Client سواء الاتصال أو التواصل، في الطريقة Processrequest أقوم بتحقق من أن HttpContext.IsWebSocketRequest قيمته true إذا كان نعم أقوم عندها بقبول الطلب و إنشاء الاتصال الذي تم تمريره لطريقة ، أخيرا استدعي الطريقة ProcessChat و التي تقوم بطباعة أي نص يرسله المستخدم، حسنا للنظر الأن إلى شفرة SignalR:
public void Send(string message)
{    
    // Call the ProcessWSChat method to update clients.   
     Clients.All.ProcessWSChat(message);
}

صدق أو لا تصدق لا يهم كثيرا J و لكن السطرين في الأعلى تقومان بنفس العمل للشفرة السابقة، يجب أن تلاحظ هنا بأن SignalR تولى مهمة إدارة الاتصالات و إنشاء كائنات الـ Web socket و ارسال و تبادل البيانات بين الخادم و العملاء، يقدم لك SignalR العديد من الـ API التي يمكنك استخدامها لتنفيذ ما تريد دون ان تقلق حيال أي شيء يتعلق مثلا بـ Web Socket على سبيل المثال ليس الحصر الشفرة اليدوية بتعامل مع الـ Web socket تصبح قيمتها صفراُ عند الحديث عن عملاء لا تدعم منصات العمل الخاص بهم الـ Web socket ربما تتفاجأ إذا أخبرتك أن II7 حتى الإصدار السابع منه لم يكن يدعم الـ Web Socket أيضا Chrome حتى الإصدار الـ 34 و IE حتى الإصدار 10 و Firefox حتى الإصدار 5 و Safari حتى الإصدار الـ 5 ، تتفهم SignalR هذا الأمر و عندما تقوم بتحاور مع عميل لا يدعم Web socket فيها تستخدم Forever frame أو Server Event أو Ajax long polling كوسيلة بدية لتواصل مع العميل دون ادنى تدخل من قبلك على الإطلاق فـ SignalR يحدد أي تقنية أفضل لهذا العميل. بالإضافة إلى كل هذه المميزات فإن SignalR يدعم جميع منصات العمل تقريبا المتعلقة بـ .Net بدء بـ asp.net و الـ windows desktop ، WPF ، Windows Phone ، Office، Silverlgiht و غيرها من منصات العمل المختلفة.

إعداد و تجهيز مشروع للعمل مع SignalR

إن إعداد مشروع للعمل أكثر بساطة من كتابة الشفرة نفسها لن يتغير عليك شيء كثير فالأمر كله محصور في خطوتين الأولى أضافة مكتبات SignalR و الخطوة الثانية تجهيز مشروع من اجل تواصل الـ Client معه، سنقوم الأن بتجهيز مشروع من اجل حساب عدد الـ Client المتصلين خلال هذه اللحظة ، الفكرة في الامر سيكون لدينا شاشة Web forms تقوم بطاعة عدد المستخدمين المتصلين و عندم يتصل مستخدم أو يغلق المتصفح يتناقص العدد أو يزيد في حالة الاتصال، سيتحدث هذا الرقم امام المستخدمين دون حاجتهم لتحديث الصفحة نهائيا، لنبدأ.

· افتح Visual Studio و قم بإنشاء مشروع جديد من نوع asp.net empty application ، اعد تسمية المشروع إلى ConnectionCounter ، الصورة:

clip_image002

· الخطوة التالية أضافة SignalR ، لعمل ذلك توجه إلى شاشة Solution Explorer حدد على reference ثم اضغط بزر الفأرة الأيمن حدد Mange NuGet ستظهر امامك شاشة Mange NuGet في صندوق البحث ابحث عن SignalR، الصورة

clip_image004
  • اضغط على Install لتثبيت مكتبات SignalR .
  • قد تطلب منك Microsoft الموافقة على بعض الشروط بتأكيد وافق بدون قراءه.
  • الخطوة التالية هي اخبرا asp.net بضرورة عمل Mapping من أجل SignalR لعمل ذلك قم بإضافة ملف جديد من النوع Owin و يجب أن يكون اسمه Startup ، في الطريقة Configuration اكتب الشفرة التالية :
public void Configuration(IAppBuilder app)
{  
  app.MapSignalR();
}
  • الأن نحتاج ان نقوم ببناء الـ Hub ، لا تقلق حيال ما هو الـ Hub حاليا، أضف Class جديد بعنوان ConnectionCounter و جعل هذا التصنيف مشتق من الـ Hub ، الشفرة:
namespace ConnectionCounter 
{   
 using Microsoft.AspNet.SignalR;  
 using Microsoft.AspNet.SignalR.Hubs;   
 [HubName("ConnectionCounter")]
 public class ConnectionCounter : Hub    {    }}
  •  الأن سنقوم بإنشاء متغير integer يكون static بحيث يكون مشترك بين جميع الـ Clients، الفكرة أن هذا المتغير يتناقص و يزداد بحسب عدد المتصلين حاليا، الشفرة
namespace ConnectionCounter{
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
[HubName("ConnectionCounter")]  
 public class ConnectionCounter : Hub    
 {       
     static int connectionCounter = 0;  
 }
}
الان سنقوم بعمل Override لطريقة OnConnected بحيث تقوم بزيادة المتغير connectionCounter بواحد، الشفرة :
public override System.Threading.Tasks.Task OnConnected(){  
 connectionCounter += 1;  
 this.Clients.All.NumberOfConnter(connectionCounter); 
     return base.OnConnected();}
لا تقلق حيال الطريقة NumberOfCounter التابعة لطريقة All في الكائن Clients فيها طريقة Dynamic، سنقوم الأن بعمل نفس الامر عند حدث قطع الاتصال مع المستخدم و هي الطريقة OnDisconnected ، و لكن بإنقاص قيمة المتغير connectionCounter هذه المرة، الشفرة:
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled){  
connectionCounter -= 1;  this.Clients.All.NumberOfConnter(connectionCounter);  
            return base.OnDisconnected(stopCalled);}
 الأن لنقم بكتابة طريقة نقوم باستدعائها عند دخول أي عميل جديد إلى الشبكة وهو عرض قيمة المتغير connectionCounter ، الشفرة :
namespace ConnectionCounter{  
  using Microsoft.AspNet.SignalR;  
  using Microsoft.AspNet.SignalR.Hubs; 
    [HubName("ConnectionCounter")]    
   public class ConnectionCounter : Hub  
  {        static int connectionCounter = 0; 
        public void GetCounterCount()        {   
         this.Clients.All.NumberOfConnter(connectionCounter); 
          }         public override System.Threading.Tasks.Task OnConnected() 
       {            connectionCounter += 1;          
                     this.Clients.All.NumberOfConnter(connectionCounter);  
             return base.OnConnected();      
  }   
     
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)      
  {            connectionCounter -= 1;  
          this.Clients.All.NumberOfConnter(connectionCounter);       
                return base.OnDisconnected(stopCalled);       
 }    
}  
}
  • صدق او لا تصدق انتهينا الان من اعدادا الخادم، شكرا للـ Hub، الأن سنقوم بعمل بواجهة لتواصل مع هذا الـ Hub ، لعمل ذلك سنستخدم الـ JavaScript .
  • أضف صفحة Web Forms جديدة إلى المشروع ثم أضف ملفين JavaScript للتعامل مع SignalR، داخل الـ Head ، الشفرة:
<script src="Scripts/jquery-1.6.4.js"></script><script src="Scripts/jquery.signalR-2.1.2.js"></script>
  •  أخيرا قم بكتابة شفرة JavaScript التاليه من اجل التواصل مع الـ Hub ، الشفرة:
<script type="text/javascript">
   2: $(function () {         
   3:    var con = $.hubConnection();     
   4:    var hub = con.createHubProxy("ConnectionCounter"); 
   5   hub.on('NumberOfConnter'، function (i) { 
   6:                $('#ConnectionCounter').text(i);   
   7:          });       
   8:      con.start(function () 
   9: { hub.invoke("GetCounterCount"); });      
  10:   })    
</script>
  •  في الشفرة التي أقوم أولا بإنشاء كائن من نوع Hub ثم أقوم بتحديد الـ Hub الخاص به عن طريق createHubProxy مررا لها الـ HubName ، أخير أقوم بإنشاء طريقة اسمها NumberOfCounter و أرسلها للـ Hub الذي سيقوم ببنائها بشكل تلقائي، تأخذ هذه الطريقة القيمة المعادة من الطريقة GetCounterCount التي يبدئ تنفيذها عندا فتح الاتصال بواسطة الطريقة start التابعة للـ Connection .\
  •  أفتح الصفحة الأن بأكثر من متصفح.
clip_image006
يجب أن يكون قد تشكل لديك صورة عما يحدث الأن ، الفكرة بسيطة جدا لديك Hub على الخادم و لديك Hub على العميل يتطلب الـ Hub على الخادم طريقة dynamic حيث يجب على العميل أن يقوم برساله له أثناء فتح الاتصال مع الخادم ، هذه هي الفكرة الرئيسة من SignalR كل شيء بعد ذلك يتمحور حول هذه النقاط الأربعة.

المزامنة مع outlook

مرحبا تحدث في المقالة السابقة كيف يمكنك التواصل مع Outlook من خلال استخدام مكتبة Micosoft.Office, بعد الانتهاء من العمل على المشروع الذي احتجت به التواصل مع الـ Outlook كان كل شيء يعمل طبيعيا من داخل visual studio حيث يمكنني تشغيل تطبيق asp.net و إضافة و تعديل و حذف مواعيد من داخل asp.net , و لكن عندما أخذت التطبيق إلى production و نشرته إلى IIS واجهة بعض المتاعب كون انه لا يمكنك التواصل مع تطبيق desktop من داخل IIS .

عندها كان علي أن اجد طريقة للمزامنة مع outlook و طبعا يظهر الحل القديم و معهود عند المحاولة لتبادل البيانات بين التطبيق و هي تخزين السطر في جدول معين في قاعدة البيانات مع الاشارة إلى ان هذا السطر بحاجة إلى نشر , ثم يقوم تطبيق اخر مثلا windows Service يعمل كل فترة معينة بالبحث عن السطور  التي تحتاج نشر إلى الـ Outlook في هذه الحالة, ثم يقوم بنشرها مع تعديل خصائصها بأنه تم نشرها.
image

الحل جيد إلى حد كبير و لكنه غير عملي لأسباب كثيرة أولا المزامنة ليست حقيقة بمعنى هناك فارق زمني بين الضغط على أمر نشر في تطبيق asp.net و بين timer control التي تقوم بالبحث كل 10 ثواني مثلا عن بيانات جديدة, المشكلة الثانية هي استهلاك كم كبير من الموارد بشكل غير متوقع فكل عشر ثواني عليك أن تتفتح اتصال بقاعدة بيانات و تنفيذ جملة استعلام للتحقق من أن هناك مواعيد جديدة لم تنشر ثم إذا وجدت مواعد جديدة عليك نشرها و تعديل حالة نشرها في قاعدة البيانات.

هناك حل أخر لا يعتمد على خادم قاعدة البيانات و لا على IIS و إنما على المستخدم نفسه, بمعنى أخر ما اريد عمله الأن هو عندما يقوم المستخدم بضغط على نشر سيقوم تطبيق asp.net بإخبار الـ Console Application في حالتي بأن هناك موعد جديدة عليك نشره راجع قاعدة البيانات , انظر الصورة
image

رائع صحيح , بهذه الطريقة سأتخلص تمام من فكرة المؤقت المعاقة :-) , سيكون هناك مزامنة حقيقة بتو و اللحظة للبيانات سأخفض بشكل كبير استهلاك موارد الاتصال و قاعدة البيانات و غيرها, و هنا يأتي دور SingalR , شاهد الفيديو:



الفكرة بسيطة جدا سأقوم أولا بإنشاء hub جديد اسميته NotificationHub حيث يتولى هذا الـ Hub مهمة التواصل مع الـ Client يحتوي الـ Hub على طريقة واحدة هي notifyClientsToStartSync و التي تستعدي الطريقة All الموجدة داخل الكائن Client , لاحظ هنا بان NotifyClients عبارة عن كائن Dynimc , الشفرة :
   1: // <copyright file="Error.aspx.cs" company="GCC">
   2: //     GCC. All rights reserved.
   3: // </copyright>
   4: // <author>ALGHABBAN</author>
   5: // <email>a.alghabban@gcc-sg.org</email>
   6:  
   7: namespace Agenda.Web
   8: {
   9:     using Microsoft.AspNet.SignalR;
  10:  
  11:     /// <summary>
  12:     /// Notification Hub
  13:     /// </summary>
  14:     public class NotificationHub : Hub
  15:     {
  16:         /// <summary>
  17:         /// Gets or sets withere Client Need To Sync or not 
  18:         /// </summary>
  19:         public static bool ClientNeedToSync { get; set; }
  20:  
  21:         /// <summary>
  22:         /// notify ClientsTo Start Sync
  23:         /// </summary>
  24:         /// <param name="clientDoneFormSynicinig">client Done Form Synicinig</param>
  25:         public void notifyClientsToStartSync(bool clientDoneFormSynicinig)
  26:         {
  27:             if(ClientNeedToSync)
  28:             {                
  29:                 Clients.All.NotifyClients();
  30:                 ClientNeedToSync = clientDoneFormSynicinig;
  31:             }            
  32:         }
  33:     }
  34: }

يجب علي أن اقوم بنقطة أخرى هو self-hosting للـ SignalR , حيث يجب على التطبيق asp.net ان يقوم باستضافة تطبيق SignalR بداخلة و بتالي أصبح هو كخادم و أن يعمل بمجرد عمل التطبيق, لعمل ذلك عليا استخدام الحدث Application_Start و استدعاء الطريقة WebApp.Start ممرا لها مكان أو رابط الاستضافة , الشفرة :

   1: /// <summary>
   2: /// Start the SignalR Server when the application start
   3: /// </summary>
   4: /// <param name="sender">sender object</param>
   5: /// <param name="e">e object</param>
   6: protected void Application_Start(object sender, EventArgs e)
   7: {
   8:  string ServerURI = System.Configuration.ConfigurationManager.AppSettings["SignalRLink"].ToString();
   9:  
  10:     try
  11:     {
  12:         SignalR = WebApp.Start(ServerURI);
  13:     }
  14:     catch (Exception ex)
  15:     {
  16:         new Logger(ex);
  17:     }                        
  18: }

نأتي لشفرة الاهم و هي اخبار العميل بأن هناك تحديثات جديدة طرأت و لعمل ذلك سنقوم بإنشاء طريقة اسميتها NotifiyClients موجودة داخل التصنيف Global و هي Static , الشفرة :

   1: /// <summary>
   2: /// Notifi All Client change happend
   3: /// </summary>
   4: public static void NotifiyClients()
   5: {
   6:     var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
   7:     context.Clients.All.notifyClientsToStartSync(true);
   8: }

لأن كل ما علي فعله هو استدعاء هذه الطريقة في الاماكن التي اريدها كما شاهدة في الفيديو عند الضغط على زر نشر أو الغاء نشر في او تعديل او حذف موعد , الشفرة , على سبيل المثال في حالة الحذف :
   1: /// <summary>
   2: /// delete selected program
   3: /// </summary>
   4: /// <param name="selectedProgram">selected Program to delete</param>
   5: private void DeleteProgram(Agenda.Controller.Program selectedProgram)
   6: {
   7:     try
   8:     {
   9:         if (selectedProgram != null)
  10:         {
  11:             selectedProgram.AgentStatus = "Deleted";
  12:             selectedProgram.Update();                    
  13:             this.ProgramGridView.DataBind();
  14:             Global.NotifiyClients();
  15:         }
  16:     }
  17:     catch (Exception ex)
  18:     {
  19:         new Logger(ex);
  20:         this.Response.Redirect("~/Error.aspx");
  21:     }
  22: }

نأتي للجزء الاخر من القصة وهو الـ Client , أقوم أولا عند بداية تشغيل التطبيق بإنشاء تواجد من للـ SignalR و من ثم اقوم بعمل Proxy مستخدما الاتصال بـ، HubConnection أخيرا استخدام الطريقة On لتنفيذ الطريقة notifyClientsToStartSync التي قمت بإنشائها مسبقا ممرا لها SyncToOutLook, الشفرة :

   1: /// <summary>
   2: /// Wait To Syinc with Agenda Data Base
   3: /// </summary>
   4: /// <returns></returns>
   5: static async Task WaitToSyncFromAgendDataBase()
   6: {
   7:     try
   8:     {
   9:         Connection = new HubConnection(ServerURI);
  10:         HubProxy = Connection.CreateHubProxy("NotificationHub");
  11:         HubProxy.On<bool>("notifyClientsToStartSync", (NotificationStatus) =>
  12:         {
  13:             if (NotificationStatus)
  14:             {
  15:                 SyncToOutlook();
  16:             }
  17:         });
  18:  
  19:         await Connection.Start();
  20:         Console.WriteLine("Connted to the server");
  21:     }
  22:     catch (Exception ex)
  23:     {
  24:         Console.Write(ex.Message);
  25:     }
  26: }
ليس مهم ما هي الشفرة المكتوبة من اجل الطريقة SyncToOutLook ما هو مهم الان انني استعديت هذه الطريقة عنده الحاجة لها فقط, موفرة بذلك الكثير من الوقت و الجهد و الموارد و مزامنة حقيقة بين التطبيق و بين outlook.