[.NET][SignalR] 由 Server 呼叫 JavaScript–使用 SignalR 實作 Push 訊息模式

在前一個範例中,我們己經實作出來一個簡單的應用程式,而這次我們要來展示 SignalR 的另一個功能:由伺服端呼叫用戶端的 JavaScript 指令碼的功能,而這個功能的要求必須是要實作成 Hub 的模式,因此我們可以順便看到如何實作 Hub 類型的 SignalR 應用程式。

在前一個範例中,我們己經實作出來一個簡單的應用程式,而這次我們要來展示 SignalR 的另一個功能:由伺服端呼叫用戶端的 JavaScript 指令碼的功能,而這個功能的要求必須是要實作成 Hub 的模式,因此我們可以順便看到如何實作 Hub 類型的 SignalR 應用程式。

一樣的,我們在專案內加入一個新的類別 PushNotification,並且設定繼承 Hub 類別 (這是 Hub 應用程式的要求) 以及實作 IConnected, IDisconnect 兩個介面:


using System.Threading;
using System.Threading.Tasks;
using SignalR;
using SignalR.Hubs;
using SignalR.Infrastructure;
using SignalR.Hosting.AspNet;

namespace ServerPushMessageApplication
{
    [HubName("push")]
    public class PushNotification : Hub, IConnected, IDisconnect
    {
         // ...
    }
}

接著,加入連線保留的物件以及實作 IConnected/IDisconnect 方法:


private static List<string> _connectionIds = new List<string>();
private static Task pushTask = null;
                        
public Task Disconnect()
{
    if (_connectionIds.Contains(this.Context.ConnectionId))
        _connectionIds.Remove(this.Context.ConnectionId);

    return null;
}

public Task Connect()
{
    _connectionIds.Add(this.Context.ConnectionId);

    return null;
}

public Task Reconnect(IEnumerable<string> groups)
{
    return null;
}

這段程式碼的用意是,在連線進到 Hub 時,將連線代碼加到連線用戶的集合中,等會就會使用到,因為我們會依照用戶端的 ID 來呼叫用戶端指令碼。

接著,我們加入真正重要的程式碼,也就是 Push 用的程式碼:


public PushNotification()
{
    pushTask = new Task(() =>
        {
            while (true)
            {
                if (_connectionIds.Count > 0)
                {
                    foreach (string id in _connectionIds)
                        Clients[id].updateDateTime(DateTime.Now.ToString());
                }

                Thread.Sleep(10000);
            }
        });

    pushTask.Start();
}

~PushNotification()
{
    pushTask = null;
}

這段程式碼的用意是,會在每十秒鐘時呼叫用戶端的 updateDateTime() 指令碼,並且將參數傳回用戶端,參數的排列與伺服端傳入的順序相同,等一下我們會實作到用戶端的 JavaScript,屆時就可以比較一下。

接著,我們在 Global.asax 的程式中,加入啟用 Hub 的程式碼:


public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        // enable Hub
        RouteTable.Routes.MapHubs();

        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
    }
}

至此,伺服器端就寫好了,看起來不難吧。

接著,在專案中加入新的 View,然後加入下列的 HTML/JavaScript:

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
    <script src="http://code.jquery.com/jquery-1.7.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery.signalR-0.5.2.min.js" type="text/javascript"></script>
    <script src="/signalr/hubs" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            var push = $.connection.push;

            push.updateDateTime = function (d) {
                $("#messages").append($("<li />").text(d));
            };

            $.connection.hub.start();

        });
    </script>
</head>
<body>
    
    <ul id="messages">
    </ul>
</body>
</html>

用戶端部份和 Persistent Connection 的不同,就是在用戶端必須要加入 “/singler/hubs” 這個指令碼來源,它會產生 hub 的 metadata,如果沒有它的話,用戶端針對 hub 的指令碼 ($.connection.push) 會失效。

其中比較重要的是 push.updateDateTime 的實作,它就是要負責接收來自伺服端的資料並在用戶端做處理的,但它必須要掛在 hub 物件之下,否則沒辦法被呼叫,這也是為什麼 SignalR 能做伺服端呼叫用戶端的原因。

完成後,執行結果如下:

image

可以參考用戶端程式碼,可發現用戶端只有啟動連線而己,並沒有主動呼叫伺服器端,但伺服器端卻可以傳回資料到用戶端,這就是 SignalR 的神奇之處 (等以後我們討論到細節時,你就不會覺得它神奇了 :) )。

 

Reference: https://github.com/SignalR/SignalR/wiki/QuickStart-Hubs