在 Asp.NET MVC 中使用 SignalR 實作推送功能
羅朝輝 ( http://blog.csdn.net/kesalin )
CC許可,轉載請注明出處
一,簡介
Signal 是微軟支援的一個運作在 Dot NET 平台上的 html websocket 架構。它出現的主要目的是實作伺服器主動推送(Push)消息到用戶端頁面,這樣用戶端就不必重新發送請求或使用輪詢技術來擷取消息。
可通路其官方網站:https://github.com/SignalR/ 擷取更多資訊。
二,實作機制
SignalR 的實作機制與 .NET WCF 或 Remoting 是相似的,都是使用遠端代理來實作。在具體使用上,有兩種不同目的的接口:PersistentConnection 和 Hubs,其中 PersistentConnection 是實作了長時間的 Javascript 輪詢(類似于 Comet),Hub 是用來解決實時資訊交換問題,它是利用 Javascript 動态載入執行方法實作的。SignalR 将整個連接配接,資訊交換過程封裝得非常漂亮,用戶端與伺服器端全部使用 JSON 來交換資料。
下面就 Hubs 接口的使用來講講整個流程:
1,在伺服器端定義對應的 hub class;
2,在用戶端定義 hub class 所對應的 proxy 類;
3,在用戶端與伺服器端建立連接配接(connection);
4,然後用戶端就可以調用 proxy 對象的方法來調用伺服器端的方法,也就是發送 request 給伺服器端;
5,伺服器端接收到 request 之後,可以針對某個/組用戶端或所有用戶端(廣播)發送消息。
三,Hub 示例教程
1,工具準備
SignalR 運作在 .NET 4.5 平台上,是以需要安裝 .NET 4.5。為了友善示範,本示例使用 ASP.NET MVC 在 Win 7 系統來實作。這需要安裝 ASP.NET MVC 3 或 ASP.NET MVC 4。
2,建立工程
打開 VS2010/VS2012 建立名為 SignalRTutorial 的 ASP.NET MVC 3 Web Application 工程,選擇 Internet Application 模闆, Razor 視圖引擎以及勾選 Use HTMl 5 标簽。

3,安裝 SignalR
打開 NuGet 的 package manager console(Tools->Library package manager),輸入:install-package SignalR.Sample,回車安裝。如圖所示:
4,實作 Hub 伺服器端代碼
向工程中建立 SignalR 目錄,在其中添加 ChatHub.cs 檔案,内容如下:
namespace SignalTutorial.SignalR
{
[HubName("chat")]
public class Chat : Hub
{
public void Send(string clientName, string message)
{
//var toSelfinfo = "You had sent message " + message;
//Caller.addSomeMessage(clientName, toSelfinfo);
// Call the addMessage method on all clients
Clients.addSomeMessage(clientName, message);
//Clients[Context.ConnectionId].addSomeMessage(clientName, data);
}
}
}
在上面的代碼中:
1),HubName 這個特性是為了讓用戶端知道如何建立與伺服器端對應服務的代理對象,如果沒有設定該屬性,則以伺服器端的服務類名字作為 HubName 的預設值;
2),Chat 繼承自 Hub,從下面 Hub 的接口圖可以看出:Hub 支援向發起請求者(Caller),所有用戶端(Clients),特定組(Group) 推送消息。
3),public void Send(string clientName, string message) 這個接口是被用戶端通過代理對象調用的;
4),Clients 是 Hub 的屬性,表示所有連結的用戶端頁面,它和 Caller 一樣是 dynamic,因為要直接對應到 Javascript 對象;
5),Clients.addSomeMessage(clientName, message); 表示伺服器端調用用戶端的 addSomeMessage 方法,這是一個 Javascript 方法,進而給用戶端推送消息。
6),總結:這裡實作的服務很簡單,就是當一個用戶端調用 Send 方法向伺服器發送 message 後,伺服器端負責将該 message 廣播給所有的用戶端(也可以給特定組或特定用戶端,見屏蔽代碼),以實作聊天室的功能。
5,實作 Hub 用戶端代碼
1),引用 SignalR Javascript
為了簡化引用 SignalR 腳本操作,我直接在 View/Shared/_Layout.cshtml 中引入 SignalR 及其他腳本:
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-1.6.4.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-ui-1.8.24.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.signalR-0.5.3.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/signalr/hubs")" type="text/javascript"></script>
</head>
注意:signalR 依賴于 jquery,是以 signalR 必須放在 jquery 之後,而 hubs 又必須放在 signalR 之後。
然後在 body 部分加入 HubChat Tab:
<li>@Html.ActionLink("HubChat", "HubChat", "Home")</li>
2),生成通路頁面
在 HomeController 中添加如下方法:
public ActionResult HubChat()
{
ViewBag.ClientName = "使用者-" + Rnd.Next(10000, 99999);
return View();
}
這裡由伺服器根據随機數來設定用戶端的名字,不夠嚴謹,因為随機數生成的名字不是唯一的的,在這裡僅為簡化示範,實際應用中應該使用 GUID 。
然後生成對應的 View:HubChat.cshtml
@model dynamic
@{
ViewBag.Title = "title";
}
<script src="@Url.Content("~/Scripts/hubDemo.js")" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
});
</script>
<h2>Hub Chat</h2>
<div>
<input type="text" id="Placeholder" value="@ViewBag.ClientName" hidden="true"/>
<input type="text" id="msg" />
<input type="button" id="broadcast" value="廣播" />
<br />
<br />
<h3>
消息記錄: (你是:<span id="MyClientName">@ViewBag.ClientName</span>):
</h3>
<ul id="messages">
</ul>
</div>
在上面的頁面代碼中,我添加了名為 hubDemo.js 的腳本,這将在下面介紹;此外還有一個id 為 Placeholder 的隐藏 input 控件,這是為了向 Javascript 中傳遞用戶端的名字。
3),編寫 Javascript
向 Scripts 目錄添加新的 Javescript 腳本:hubDemo.js。其内容如下:
$(function () {
var myClientName = $('#Placeholder').val();
// Proxy created on the fly
var chat = $.connection.chat;
// Declare a function on the chat hub so the server can invoke it
chat.addSomeMessage = function (clientName, message) {
writeEvent('<b>' + clientName + '</b> 對大家說: ' + message, 'event-message');
};
$("#broadcast").click(function () {
// Call the chat method on the server
chat.send(myClientName, $('#msg').val())
.done(function () {
console.log('Sent message success!');
})
.fail(function (e) {
console.warn(e);
});
});
// Start the connection
$.connection.hub.start();
//A function to write events to the page
function writeEvent(eventLog, logClass) {
var now = new Date();
var nowStr = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds();
$('#messages').prepend('<li class="' + logClass + '"><b>' + nowStr + '</b> ' + eventLog + '.</li>');
}
});
上面代碼有詳細的注釋,下面再講講關鍵之處:
1,首先擷取用戶端頁面的名字;
2,然後通過 $.connection.chat 建立對應伺服器端 Hub 類的代理對象 chat;
3,定義用戶端的 Javascript 方法 addSomeMessage ,伺服器通過 dynamic 方式調用用戶端的該方法以實作推送功能。在這裡每當收到伺服器推送來的消息,就在用戶端頁面的 messages 清單表頭插入該消息。
4,當點選廣播按鈕時,用戶端通過代理對象調用伺服器端的 send 方法以實作向伺服器發送消息。
5,通過 $.connection.hub.start(); 語句打開連結。
6),編譯運作 Hub 示例
在多個浏覽器視窗打開頁面,效果如下:
四,Persistent Connection 示例教程
1,實作伺服器端代碼
1),編寫伺服器 PersistentConnection 代碼
向工程中 SignalR 目錄中添加 PersistentConnection.cs 檔案,内容如下:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using SignalR;
namespace SignalTutorial.SignalR
{
public class MyConnection : PersistentConnection
{
protected override Task OnConnectedAsync(IRequest request, string connectionId)
{
return Connection.Broadcast("Connection " + connectionId + " connected");
}
protected override Task OnReconnectedAsync(IRequest request, IEnumerable<string> groups, string clientId)
{
return Connection.Broadcast("Client " + clientId + " re-connected");
}
protected override Task OnReceivedAsync(IRequest request, string connectionId, string data)
{
var info = data + ". ConnectionId is [" + connectionId + "]";
// return Connection.Send(connectionId, info);
// Broadcast data to all clients
return Connection.Broadcast(info);
}
protected override Task OnDisconnectAsync(string connectionId)
{
return Connection.Broadcast("Connection " + connectionId + " disconncted");
}
protected override Task OnErrorAsync(Exception error)
{
return Connection.Broadcast("Error ocurred " + error);
}
}
}
在上面的代碼中:
1,MyConnection 繼承自 PersistentConnection,這樣我們就能在用戶端連接配接,重連接配接,斷開連接配接,發送消息以及連接配接出錯的情況下進行相關的處理。從下面的 PersistentConnection 接口中可以看到,PersistentConnection 同樣支援組進行推送。
2,推送消息由 PersistentConnection 的屬性 Connection 來提供,它繼承自 IConnection 接口,該接口提供兩個函數來實作對特定用戶端的推送和廣播功能。
System.Threading.Tasks.Task Send(string signal, object value)
System.Threading.Tasks.Task Broadcast(object value)
2),配置通路路由
為了支援用戶端通路,需要在路由表中進行配置。打開 Global.asax.cs ,修改 Application_Start() 函數如下:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteTable.Routes.MapConnection<MyConnection>("echo", "echo/{*operation}");
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
// Make connections wait 50s maximum for any response. After
// 50s are up, trigger a timeout command and make the client reconnect.
GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(50);
//DisconnectTimeout
//HeartBeatInterval
//KeepAlive
}
在上面的代碼中,我将 echo 及其子路徑的通路映射到 MyConnection 上,并設定連接配接逾時時間為 50 s。在這裡還可以設定其他的一些參數,如斷連逾時時間,心跳間隔等。
2,實作用戶端代碼
1),生成通路頁面
在前面三 Hub 示例教程的基礎上,我們向該工程加入使用 Persistent Connection 的示範。和前面一樣,向 _Layout.cshtml 中加入 PersistentChat Tab:
<li>@Html.ActionLink("PersistentChat", "PersistentChat", "Home")</li>
然後在 HomeController 中添加如下方法:
public ActionResult PersistentChat()
{
ViewBag.ClientName = "使用者-" + Rnd.Next(10000, 99999);
return View();
}
這裡由伺服器根據随機數來設定用戶端的名字,不夠嚴謹,因為随機數生成的名字不是唯一的的,在這裡僅為簡化示範,實際應用中應該使用 GUID 。
然後生成對應的 頁面: PersistentChat.cshtml:
@model dynamic
@{
ViewBag.Title = "title";
}
<script src="@Url.Content("~/Scripts/persistent.js")" type="text/javascript"></script>
<h2>Persistent Chat</h2>
<div>
<input type="text" id="Placeholder" value="@ViewBag.ClientName" hidden="true"/>
<input type="text" id="msg" />
<input type="button" id="broadcast" value="廣播" />
<br />
<br />
<h3>
消息記錄: (你是:<span id="MyClientName">@ViewBag.ClientName</span>):
</h3>
<ul id="messages">
</ul>
</div>
在上面的頁面代碼中,我添加了名為 persistent.js 的腳本,這将在下面介紹;此外還有一個id 為 Placeholder 的隐藏 input 控件,這是為了向 Javascript 中傳遞用戶端的名字。
2),編寫 Javascript
向 Scripts 目錄添加新的 Javescript 腳本:persistent.js。其内容如下:
$(function () {
var myClientName = $('#Placeholder').val();
var connection = $.connection('/echo');
connection.received(function (data) {
var msg = new String(data);
var index = msg.indexOf("#");
var clientName = msg.substring(0, index);
var content = msg.substring(index + 1);
if (clientName == null || clientName == "") {
writeEvent('<b>' + "系統消息" + '</b>: ' + content, 'event-message');
}
else {
writeEvent('<b>' + clientName + '</b> 對大家說: ' + content, 'event-message');
}
});
connection.start();
$("#broadcast").click(function () {
var msg = myClientName + "#" + $('#msg').val();
connection.send(msg);
});
//A function to write events to the page
function writeEvent(eventLog, logClass) {
var now = new Date();
var nowStr = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds();
$('#messages').prepend('<li class="' + logClass + '"><b>' + nowStr + '</b> ' + eventLog + '.</li>');
}
});
上面的代碼基本與前面的 Hub 實作相同,在此就不再一一講述。有兩點值得說明:
1,建立連接配接時,指定路徑為 "/echo",該路徑在伺服器端的路由映射表被映射為 MyConnection,因而這個連接配接就被指向前面提供的 MyConnection。
2,将 clientName 資訊放入 message 中,并用 # 将 clientName 和消息内容連接配接成一個 msg。
3,編譯運作 Persistent 示例
五,引用
SignalR:
https://github.com/SignalR/
利用SignalR實現遠端程式遙控功能:
http://blog.darkthread.net/post-2012-07-10-signalr-remote-controller.aspx
一個很酷的同步操作表格的示例(使用 jTable ):
http://www.codeproject.com/Articles/315938/Real-time-Asynchronous-Web-Pages-using-jTable-Sign
組通知示例:
http://www.codeproject.com/Articles/404662/SignalR-Group-Notifications