Asp.NET MVC中使用SignalR實(shí)現(xiàn)推送功能
一、簡(jiǎn)介
Signal 是微軟支持的一個(gè)運(yùn)行在 Dot NET 平臺(tái)上的 html websocket 框架。它出現(xiàn)的主要目的是實(shí)現(xiàn)服務(wù)器主動(dòng)推送(Push)消息到客戶端頁(yè)面,這樣客戶端就不必重新發(fā)送請(qǐng)求或使用輪詢技術(shù)來(lái)獲取消息。
可訪問(wèn)其官方網(wǎng)站:https://github.com/SignalR/ 獲取更多資訊。
二、Asp.net SignalR 是個(gè)什么東東
Asp.net SignalR是微軟為實(shí)現(xiàn)實(shí)時(shí)通信的一個(gè)類庫(kù)。一般情況下,SignalR會(huì)使用JavaScript的長(zhǎng)輪詢(long polling)的方式來(lái)實(shí)現(xiàn)客戶端和服務(wù)器通信,隨著Html5中WebSockets出現(xiàn),SignalR也支持WebSockets通信。另外SignalR開(kāi)發(fā)的程序不僅僅限制于宿主在IIS中,也可以宿主在任何應(yīng)用程序,包括控制臺(tái),客戶端程序和Windows服務(wù)等,另外還支持Mono,這意味著它可以實(shí)現(xiàn)跨平臺(tái)部署在Linux環(huán)境下。
SignalR內(nèi)部有兩類對(duì)象:
Http持久連接(Persisten Connection)對(duì)象:用來(lái)解決長(zhǎng)時(shí)間連接的功能。還可以由客戶端主動(dòng)向服務(wù)器要求數(shù)據(jù),而服務(wù)器端不需要實(shí)現(xiàn)太多細(xì)節(jié),只需要處理PersistentConnection 內(nèi)所提供的五個(gè)事件:OnConnected, OnReconnected, OnReceived, OnError 和 OnDisconnect 即可。
Hub(集線器)對(duì)象:用來(lái)解決實(shí)時(shí)(realtime)信息交換的功能,服務(wù)端可以利用URL來(lái)注冊(cè)一個(gè)或多個(gè)Hub,只要連接到這個(gè)Hub,就能與所有的客戶端共享發(fā)送到服務(wù)器上的信息,同時(shí)服務(wù)端可以調(diào)用客戶端的腳本。
SignalR將整個(gè)信息的交換封裝起來(lái),客戶端和服務(wù)器都是使用JSON來(lái)溝通的,在服務(wù)端聲明的所有Hub信息,都會(huì)生成JavaScript輸出到客戶端,.NET則依賴Proxy來(lái)生成代理對(duì)象,而Proxy的內(nèi)部則是將JSON轉(zhuǎn)換成對(duì)象。
SignalR既然是為實(shí)時(shí)而生的,這樣就決定了其使用場(chǎng)所。具體適用情景有如下幾點(diǎn):
聊天室,如在線客服系統(tǒng),IM系統(tǒng)等
股票價(jià)格實(shí)時(shí)更新
消息的推送服務(wù)
游戲中人物位置的實(shí)時(shí)推送
目前,我所在公司在開(kāi)發(fā)的就是在線客服系統(tǒng)。
三、實(shí)現(xiàn)機(jī)制
SignalR 的實(shí)現(xiàn)機(jī)制與 .NET WCF 或 Remoting 是相似的,都是使用遠(yuǎn)程代理來(lái)實(shí)現(xiàn)。在具體使用上,有兩種不同目的的接口:PersistentConnection 和 Hubs,其中 PersistentConnection 是實(shí)現(xiàn)了長(zhǎng)時(shí)間的 Javascript 輪詢(類似于 Comet),Hub 是用來(lái)解決實(shí)時(shí)信息交換問(wèn)題,它是利用 Javascript 動(dòng)態(tài)載入執(zhí)行方法實(shí)現(xiàn)的。SignalR 將整個(gè)連接,信息交換過(guò)程封裝得非常漂亮,客戶端與服務(wù)器端全部使用 JSON 來(lái)交換數(shù)據(jù)。
下面就 Hubs 接口的使用來(lái)講講整個(gè)流程:
1.在服務(wù)器端定義對(duì)應(yīng)的 hub class;
2.在客戶端定義 hub class 所對(duì)應(yīng)的 proxy 類;
3.在客戶端與服務(wù)器端建立連接(connection);
4.然后客戶端就可以調(diào)用 proxy 對(duì)象的方法來(lái)調(diào)用服務(wù)器端的方法,也就是發(fā)送 request 給服務(wù)器端;
5.服務(wù)器端接收到 request 之后,可以針對(duì)某個(gè)/組客戶端或所有客戶端(廣播)發(fā)送消息。
四、使用Asp.net SignalR在Web端實(shí)現(xiàn)廣播消息
通過(guò)第二部分的介紹,相信大家對(duì)Asp.net SignalR有了一個(gè)初步的了解,接下來(lái)通過(guò)兩個(gè)例子來(lái)讓大家加深對(duì)SignalR運(yùn)行機(jī)制的理解。第一個(gè)例子就是在Web端如何使用SignalR來(lái)實(shí)現(xiàn)廣播消息。
使用Visual Studio 2013,創(chuàng)建一個(gè)MVC工程
通過(guò)Nuget安裝SignalR包。右鍵引用-》選擇管理Nuget程序包-》在出現(xiàn)的窗口中輸入SignalR來(lái)找到SignalR包進(jìn)行安裝。
安裝SignalR成功后,SignalR庫(kù)的腳本將被添加進(jìn)Scripts文件夾下。具體如下圖所示:
向項(xiàng)目中添加一個(gè)SignalR集線器(v2)并命名為ServerHub。
將下面代碼填充到剛剛創(chuàng)建的ServerHub類中。
using System; using Microsoft.AspNet.SignalR; namespace SignalDemo { public class ServerHub : Hub { private static readonly char[] Constant = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; /// <summary> /// 供客戶端調(diào)用的服務(wù)器端代碼 /// </summary> /// <param name="message"></param> public void Send(string message) { var name = GenerateRandomName(4); // 調(diào)用所有客戶端的sendMessage方法 Clients.All.sendMessage(name, message); } /// <summary> /// 產(chǎn)生隨機(jī)用戶名函數(shù) /// </summary> /// <param name="length">用戶名長(zhǎng)度</param> /// <returns></returns> public static string GenerateRandomName(int length) { var newRandom = new System.Text.StringBuilder(62); var rd = new Random(); for (var i = 0; i < length; i++) { newRandom.Append(Constant[rd.Next(62)]); } return newRandom.ToString(); } } }
創(chuàng)建一個(gè)Startup類,如果開(kāi)始創(chuàng)建MVC項(xiàng)目的時(shí)候沒(méi)有更改身份驗(yàn)證的話,這個(gè)類會(huì)默認(rèn)添加的,如果已有就不需要重復(fù)添加了。按照如下代碼更新Startup類。
using Microsoft.Owin; using Owin; [assembly: OwinStartupAttribute(typeof(SignalDemo.Startup))] namespace SignalDemo { public partial class Startup { #region MyRegion public void Configuration(IAppBuilder app) { app.MapSignalR(); ConfigureAuth(app); } #endregion } }
在Home控制器中創(chuàng)建一個(gè)Chat Action方法
using System.Web.Mvc; namespace SignalDemo.Controllers { public class HomeController : Controller { public ActionResult Chat() { return View(); } } }
在Views文件中Home文件夾中創(chuàng)建一個(gè)Chat視圖,視圖代碼如下所示:
@{ ViewBag.Title = "Chat"; } <h2>Chat</h2> <div class="container"> <input type="text" id="message" /> <input type="button" id="sendmessage" value="Send" /> <input type="hidden" id="displayname" /> <ul id="discussion"></ul> </div> @section scripts { <!--引用SignalR庫(kù). --> <script src="~/Scripts/jquery.signalR-2.2.1.min.js"></script> <!--引用自動(dòng)生成的SignalR 集線器(Hub)腳本.在運(yùn)行的時(shí)候在瀏覽器的Source下可看到 --> <script src="~/signalr/hubs"></script> <script> $(function () { // 引用自動(dòng)生成的集線器代理 var chat = $.connection.serverHub; // 定義服務(wù)器端調(diào)用的客戶端sendMessage來(lái)顯示新消息 chat.client.sendMessage = function (name, message) { // 向頁(yè)面添加消息 $('#discussion').append('<li><strong>' + htmlEncode(name) + '</strong>: ' + htmlEncode(message) + '</li>'); }; // 設(shè)置焦點(diǎn)到輸入框 $('#message').focus(); // 開(kāi)始連接服務(wù)器 $.connection.hub.start().done(function () { $('#sendmessage').click(function () { // 調(diào)用服務(wù)器端集線器的Send方法 chat.server.send($('#message').val()); // 清空輸入框信息并獲取焦點(diǎn) $('#message').val('').focus(); }); }); }); // 為顯示的消息進(jìn)行Html編碼 function htmlEncode(value) { var encodedValue = $('<div />').text(value).html(); return encodedValue; } </script> }
修改App_Start文件夾內(nèi)的RoutConfig類,將Action方法默認(rèn)設(shè)置為Chat
using System.Web.Mvc; using System.Web.Routing; namespace SignalDemo { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Chat", id = UrlParameter.Optional } ); } } }
到此,我們的例子就實(shí)現(xiàn)完成了,接下來(lái)我們先來(lái)看看運(yùn)行效果,之后再來(lái)解釋到底SignalR是如何來(lái)完成廣播消息的。運(yùn)行的運(yùn)行結(jié)果如下。
從運(yùn)行結(jié)果,你可以發(fā)現(xiàn),在任何一個(gè)窗口輸入信息并發(fā)送,所有客戶端將收到該消息。這樣的效果在實(shí)際應(yīng)用中很多,如QQ,一登錄QQ的時(shí)候都會(huì)推送騰訊廣告消息。
看完了運(yùn)行結(jié)果,接下來(lái)我們來(lái)分析下代碼,進(jìn)而來(lái)剖析下SignalR到底是如何工作的。
按照B/S模式來(lái)看,運(yùn)行程序的時(shí)候,Web頁(yè)面就與SignalR的服務(wù)建立了連接,具體的建立連接的代碼就是:$.connection.hub.start()。這句代碼的作用就是與SignalR服務(wù)建立連接,后面的done函數(shù)表明建立連接成功后為發(fā)送按鈕注冊(cè)了一個(gè)click事件,當(dāng)客戶端輸入內(nèi)容點(diǎn)擊發(fā)送按鈕后,該Click事件將會(huì)觸發(fā),觸發(fā)執(zhí)行的操作為: chat.server.send($('#message').val())。這句代碼表示調(diào)用服務(wù)端的send函數(shù),而服務(wù)端的Send韓式又是調(diào)用所有客戶端的sendMessage函數(shù),而客戶端中sendMessage函數(shù)就是將信息添加到對(duì)應(yīng)的消息列表中。這樣就實(shí)現(xiàn)了廣播消息的功能了。
看到這里,有人是否會(huì)有疑問(wèn),前面的實(shí)現(xiàn)都只用到了集線器對(duì)象,而沒(méi)有用到持久連接對(duì)象。其實(shí)并不是如此,$.connection這句代碼就是使用持久連接對(duì)象,當(dāng)然你也可以在重新OnConnected方法來(lái)查看監(jiān)控客戶端的連接情況,更新的代碼如下所示:
public class ServerHub : Hub { private static readonly char[] Constant = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; /// <summary> /// 供客戶端調(diào)用的服務(wù)器端代碼 /// </summary> /// <param name="message"></param> public void Send(string message) { var name = GenerateRandomName(4); // 調(diào)用所有客戶端的sendMessage方法 Clients.All.sendMessage(name, message); } /// <summary> /// 客戶端連接的時(shí)候調(diào)用 /// </summary> /// <returns></returns> public override Task OnConnected() { Trace.WriteLine("客戶端連接成功"); return base.OnConnected(); } /// <summary> /// 產(chǎn)生隨機(jī)用戶名函數(shù) /// </summary> /// <param name="length">用戶名長(zhǎng)度</param> /// <returns></returns> public static string GenerateRandomName(int length) { var newRandom = new System.Text.StringBuilder(62); var rd = new Random(); for (var i = 0; i < length; i++) { newRandom.Append(Constant[rd.Next(62)]); } return newRandom.ToString(); } }
這樣在運(yùn)行頁(yè)面的時(shí)候,將在輸出窗口看到“客戶端連接成功”字樣。運(yùn)行效果如下圖所示:
在第二部分介紹的時(shí)候說(shuō)道,在服務(wù)端聲明的所有Hub信息,都會(huì)生成JavaScript輸出到客戶端,為了驗(yàn)證這一點(diǎn),可以在Chrome中F12來(lái)查看源碼就明白了,具體如下圖所示:
看到上圖,你也就明白了為什么Chat.cshtml頁(yè)面需要引入"signalr/hubs"腳本庫(kù)了吧。
<!--引用SignalR庫(kù). -->
<script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>
<!--引用自動(dòng)生成的SignalR 集線器(Hub)腳本.在運(yùn)行的時(shí)候在瀏覽器的Source下可看到 -->
<script src="~/signalr/hubs"></script>
五、在桌面程序中如何使用Asp.net SignalR
上面部分介紹了SignalR在Asp.net MVC 中的實(shí)現(xiàn),這部分將通過(guò)一個(gè)例子來(lái)看看SignalR在WPF或WinForm是如何使用的。其實(shí)這部分實(shí)現(xiàn)和Asp.net MVC中非常相似,主要不同在于,Asp.net MVC中的SignalR服務(wù)器寄宿在IIS中,而在WPF中應(yīng)用,我們把SignalR寄宿在WPF客戶端中。
下面讓我們看看SignalR服務(wù)端的實(shí)現(xiàn)。
/// <summary> /// 啟動(dòng)SignalR服務(wù),將SignalR服務(wù)寄宿在WPF程序中 /// </summary> private void StartServer() { try { SignalR = WebApp.Start(ServerUri); // 啟動(dòng)SignalR服務(wù) } catch (TargetInvocationException) { WriteToConsole("一個(gè)服務(wù)已經(jīng)運(yùn)行在:" + ServerUri); // Dispatcher回調(diào)來(lái)設(shè)置UI控件狀態(tài) this.Dispatcher.Invoke(() => ButtonStart.IsEnabled = true); return; } this.Dispatcher.Invoke(() => ButtonStop.IsEnabled = true); WriteToConsole("服務(wù)已經(jīng)成功啟動(dòng),地址為:" + ServerUri); } public class ChatHub : Hub { public void Send(string name, string message) { Clients.All.addMessage(name, message); } public override Task OnConnected() { // Application.Current.Dispatcher.Invoke(() => ((MainWindow)Application.Current.MainWindow).WriteToConsole("客戶端連接,連接ID是: " + Context.ConnectionId)); return base.OnConnected(); } public override Task OnDisconnected(bool stopCalled) { Application.Current.Dispatcher.Invoke(() => ((MainWindow)Application.Current.MainWindow).WriteToConsole("客戶端斷開(kāi)連接,連接ID是: " + Context.ConnectionId)); return base.OnDisconnected(true); } } public class Startup { public void Configuration(IAppBuilder app) { // 有關(guān)如何配置應(yīng)用程序的詳細(xì)信息,請(qǐng)?jiān)L問(wèn) http://go.microsoft.com/fwlink/?LinkID=316888 // 允許CORS跨域 //app.UseCors(CorsOptions.AllowAll); app.MapSignalR(); } }
通過(guò)上面的代碼,我們SignalR服務(wù)端的實(shí)現(xiàn)就完成了,其實(shí)現(xiàn)邏輯與Asp.net MVC的代碼類似。
接下來(lái),讓我們看看,WPF客戶端是如何連接和與服務(wù)器進(jìn)行通信的。具體客戶端的實(shí)現(xiàn)如下:
public IHubProxy HubProxy { get; set; } const string ServerUri = "http://localhost:8888/signalr"; public HubConnection Connection { get; set; } public MainWindow() { InitializeComponent(); // 窗口啟動(dòng)時(shí)開(kāi)始連接服務(wù) ConnectAsync(); } /// <summary> /// 發(fā)送消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ButtonSend_Click(object sender, RoutedEventArgs e) { // 通過(guò)代理來(lái)調(diào)用服務(wù)端的Send方法 // 服務(wù)端Send方法再調(diào)用客戶端的AddMessage方法將消息輸出到消息框中 HubProxy.Invoke("Send", GenerateRandomName(4), TextBoxMessage.Text.Trim()); TextBoxMessage.Text = String.Empty; TextBoxMessage.Focus(); } private async void ConnectAsync() { Connection = new HubConnection(ServerUri); Connection.Closed += Connection_Closed; // 創(chuàng)建一個(gè)集線器代理對(duì)象 HubProxy = Connection.CreateHubProxy("ChatHub"); // 供服務(wù)端調(diào)用,將消息輸出到消息列表框中 HubProxy.On<string, string>("AddMessage", (name, message) => this.Dispatcher.Invoke(() => RichTextBoxConsole.AppendText(String.Format("{0}: {1}\r", name, message)) )); try { await Connection.Start(); } catch (HttpRequestException) { // 連接失敗 return; } // 顯示聊天控件 ChatPanel.Visibility = Visibility.Visible; ButtonSend.IsEnabled = true; TextBoxMessage.Focus(); RichTextBoxConsole.AppendText("連上服務(wù):" + ServerUri + "\r"); }
上面的代碼也就是WPF客戶端實(shí)現(xiàn)的核心代碼,主要邏輯為,客戶端啟動(dòng)的時(shí)候就調(diào)用Connection.Start方法與服務(wù)器進(jìn)行連接。然后通過(guò)HubProxy代理類來(lái)調(diào)用集線器中Send方法,而集線器中的Send方法又通過(guò)調(diào)用客戶端的addMessage方法將消息輸出到客戶端的消息框中進(jìn)行顯示,從而完成消息的推送過(guò)程。接下來(lái),讓我們看看其運(yùn)行效果:
從上面的運(yùn)行效果看出,其效果和Asp.net MVC上的效果是一樣的。
總結(jié)
到這里,本專題的所有內(nèi)容就結(jié)束了,這篇SignalR快速入門也是本人在學(xué)習(xí)SignalR過(guò)程中的一些心得體會(huì),希望可以幫助一些剛接觸SignalR的朋友快速入門。本篇主要實(shí)現(xiàn)了SignalR的廣播消息的功能,可以實(shí)現(xiàn)手機(jī)端消息推送的功能,接下來(lái)一篇將介紹如何使用SignalR實(shí)現(xiàn)一對(duì)一的聊天。
源碼下載:http://xiazai.jb51.net/201609/yuanma/ASP.NETSignalDemo.rar
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
asp.net通過(guò)HttpModule自動(dòng)在Url地址上添加參數(shù)
由于項(xiàng)目中有許多頁(yè)面需要用到cid參數(shù),所以想通過(guò)傳值cid來(lái)獲取數(shù)據(jù)。2010-01-01asp.net微信開(kāi)發(fā)(開(kāi)發(fā)者接入)
這篇文章主要介紹了asp.net微信開(kāi)發(fā)中有關(guān)開(kāi)發(fā)者接入的相關(guān)內(nèi)容,需要的朋友可以參考下2015-11-11.NET Core跨平臺(tái)執(zhí)行命令、腳本的方法詳細(xì)
這篇文章主要給大家介紹了關(guān)于.NET Core跨平臺(tái)執(zhí)行命令、腳本的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05未能加載文件或程序集“XXX”或它的某一個(gè)依賴項(xiàng)。試圖加載格式不正確的程序。
如果你將應(yīng)用程序生成x86而不是Any CPU時(shí),在64位操作系統(tǒng)中不會(huì)出錯(cuò)錯(cuò)誤,而在32位操作系統(tǒng)中可能會(huì)出現(xiàn)以下錯(cuò)誤2012-11-11Entity Framework Core生成列并跟蹤列記錄
這篇文章介紹了Entity Framework Core生成列并跟蹤列記錄的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02WPF使用代碼創(chuàng)建數(shù)據(jù)模板DataTemplate
本文詳細(xì)講解了WPF使用代碼創(chuàng)建數(shù)據(jù)模板DataTemplate的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02.NET Core對(duì)象池的應(yīng)用:設(shè)計(jì)篇
本文主要講解對(duì)象池的三個(gè)核心對(duì)象:表示對(duì)象池的ObjectPool<T>對(duì)象、對(duì)象值提供者的ObjectPoolProvider對(duì)象,已及控制池化對(duì)象創(chuàng)建與釋放行為的IPooledObjectPolicy<T>對(duì)象。感興趣的小伙伴可以參考一下這篇文章2021-09-09.NET?Core項(xiàng)目使用swagger開(kāi)發(fā)組件
這篇文章介紹了.NET?Core項(xiàng)目使用swagger開(kāi)發(fā)組件的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07