首页 > 代码库 > Asp.NET MVC 中使用 SignalR 实现推送功能

Asp.NET MVC 中使用 SignalR 实现推送功能

  • 一,简介

  • Signal 是微软支持的一个运行在 Dot NET 平台上的 html websocket 框架。它出现的主要目的是实现服务器主动推送(Push)消息到客户端页面,这样客户端就不必重新发送请求或使用轮询技术来获取消息。
  • 可访问其官方网站:https://github.com/SignalR/ 获取更多资讯。

 

  • 二、Asp.net SignalR 是个什么东东

  • Asp.net SignalR是微软为实现实时通信的一个类库。一般情况下,SignalR会使用JavaScript的长轮询(long polling)的方式来实现客户端和服务器通信,随着Html5中WebSockets出现,SignalR也支持WebSockets通信。另外SignalR开发的程序不仅仅限制于宿主在IIS中,也可以宿主在任何应用程序,包括控制台,客户端程序和Windows服务等,另外还支持Mono,这意味着它可以实现跨平台部署在Linux环境下。
  • SignalR内部有两类对象:
  • Http持久连接(Persisten Connection)对象:用来解决长时间连接的功能。还可以由客户端主动向服务器要求数据,而服务器端不需要实现太多细节,只需要处理PersistentConnection 内所提供的五个事件:OnConnected, OnReconnected, OnReceived, one rror 和 OnDisconnect 即可。
  • Hub(集线器)对象:用来解决实时(realtime)信息交换的功能,服务端可以利用URL来注册一个或多个Hub,只要连接到这个Hub,就能与所有的客户端共享发送到服务器上的信息,同时服务端可以调用客户端的脚本。
  • SignalR将整个信息的交换封装起来,客户端和服务器都是使用JSON来沟通的,在服务端声明的所有Hub信息,都会生成JavaScript输出到客户端,.NET则依赖Proxy来生成代理对象,而Proxy的内部则是将JSON转换成对象。
  • SignalR既然是为实时而生的,这样就决定了其使用场所。具体适用情景有如下几点:
  • 聊天室,如在线客服系统,IM系统等
  • 股票价格实时更新
  • 消息的推送服务
  • 游戏中人物位置的实时推送
  • 目前,我所在公司在开发的就是在线客服系统。
  • 三,实现机制

  • 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 之后,可以针对某个/组客户端或所有客户端(广播)发送消息。

 

  • 四、使用Asp.net SignalR在Web端实现广播消息

  •  通过第二部分的介绍,相信大家对Asp.net SignalR有了一个初步的了解,接下来通过两个例子来让大家加深对SignalR运行机制的理解。第一个例子就是在Web端如何使用SignalR来实现广播消息。
  • 使用Visual Studio 2013,创建一个MVC工程
  • 通过Nuget安装SignalR包。右键引用-》选择管理Nuget程序包-》在出现的窗口中输入SignalR来找到SignalR包进行安装。
  • 安装SignalR成功后,SignalR库的脚本将被添加进Scripts文件夹下。具体如下图所示:
  • 技术分享
  •  向项目中添加一个SignalR集线器(v2)并命名为ServerHub。

  • 技术分享

  •         将下面代码填充到刚刚创建的ServerHub类中。

    • 技术分享
       1 using System; 2 using Microsoft.AspNet.SignalR; 3  4 namespace SignalDemo 5 { 6     public class ServerHub : Hub 7     { 8         private static readonly char[] Constant = 9         {10             0, 1, 2, 3, 4, 5, 6, 7, 8, 9,11             a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v,12             w, x, y, z,13             A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V,14             W, X, Y, Z15         };16 17         /// <summary>18         /// 供客户端调用的服务器端代码19         /// </summary>20         /// <param name="message"></param>21         public void Send(string message)22         {23             var name = GenerateRandomName(4);24 25             // 调用所有客户端的sendMessage方法26             Clients.All.sendMessage(name, message);27         }28 29         /// <summary>30         /// 产生随机用户名函数31         /// </summary>32         /// <param name="length">用户名长度</param>33         /// <returns></returns>34         public static string GenerateRandomName(int length)35         {36             var newRandom = new System.Text.StringBuilder(62);37             var rd = new Random();38             for (var i = 0; i < length; i++)39             {40                 newRandom.Append(Constant[rd.Next(62)]);41             }42             return newRandom.ToString();43         }44     }45 }
      ServerHub Code

      创建一个Startup类,如果开始创建MVC项目的时候没有更改身份验证的话,这个类会默认添加的,如果已有就不需要重复添加了。按照如下代码更新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    }}
      Startup Code

      在Home控制器中创建一个Chat Action方法

      技术分享
       1 using System.Web.Mvc; 2  3 namespace SignalDemo.Controllers 4 { 5     public class HomeController : Controller 6     { 7         public ActionResult Chat() 8         { 9             return View();10         }11     }12 }
      Home Controller

      在Views文件中Home文件夹中创建一个Chat视图,视图代码如下所示:

      技术分享
       1 @{ 2     ViewBag.Title = "Chat"; 3 } 4  5 <h2>Chat</h2> 6  7 <div class="container"> 8     <input type="text" id="message" /> 9     <input type="button" id="sendmessage" value="http://www.mamicode.com/Send" />10     <input type="hidden" id="displayname" />11     <ul id="discussion"></ul>12 </div>13 14 @section scripts15 {16     <!--引用SignalR库. -->17     <script src="http://www.mamicode.com/~/Scripts/jquery.signalR-2.2.1.min.js"></script>18     <!--引用自动生成的SignalR 集线器(Hub)脚本.在运行的时候在浏览器的Source下可看到 -->19     <script src="http://www.mamicode.com/~/signalr/hubs"></script>20     <script>21         $(function () {22             // 引用自动生成的集线器代理23             var chat = $.connection.serverHub;24             // 定义服务器端调用的客户端sendMessage来显示新消息25 26             chat.client.sendMessage = function (name, message) {27                 // 向页面添加消息28                 $(‘#discussion‘).append(‘<li><strong>‘ + htmlEncode(name)29                     + ‘</strong>: ‘ + htmlEncode(message) + ‘</li>‘);30             };31             // 设置焦点到输入框32             $(‘#message‘).focus();33             // 开始连接服务器34             $.connection.hub.start().done(function () {35                 $(‘#sendmessage‘).click(function () {36                     // 调用服务器端集线器的Send方法37                     chat.server.send($(‘#message‘).val());38                     // 清空输入框信息并获取焦点39                     $(‘#message‘).val(‘‘).focus();40                 });41             });42         });43 44         // 为显示的消息进行Html编码45         function htmlEncode(value) {46             var encodedValue = http://www.mamicode.com/$(‘
      ).text(value).html();47 return encodedValue;48 }49 </script>50 }
      View Chat Code

       修改App_Start文件夹内的RoutConfig类,将Action方法默认设置为Chat

      技术分享
       1 using System.Web.Mvc; 2 using System.Web.Routing; 3  4 namespace SignalDemo 5 { 6     public class RouteConfig 7     { 8         public static void RegisterRoutes(RouteCollection routes) 9         {10             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");11             routes.MapRoute(12                 name: "Default",13                 url: "{controller}/{action}/{id}",14                 defaults: new { controller = "Home", action = "Chat", id = UrlParameter.Optional }15             );16         }17     }18 }
      RouteConfig Code

      到此,我们的例子就实现完成了,接下来我们先来看看运行效果,之后再来解释到底SignalR是如何来完成广播消息的。运行的运行结果如下。技术分享

      从运行结果,你可以发现,在任何一个窗口输入信息并发送,所有客户端将收到该消息。这样的效果在实际应用中很多,如QQ,一登录QQ的时候都会推送腾讯广告消息。

        看完了运行结果,接下来我们来分析下代码,进而来剖析下SignalR到底是如何工作的。

        按照B/S模式来看,运行程序的时候,Web页面就与SignalR的服务建立了连接,具体的建立连接的代码就是:$.connection.hub.start()。这句代码的作用就是与SignalR服务建立连接,后面的done函数表明建立连接成功后为发送按钮注册了一个click事件,当客户端输入内容点击发送按钮后,该Click事件将会触发,触发执行的操作为: chat.server.send($(‘#message‘).val())。这句代码表示调用服务端的send函数,而服务端的Send韩式又是调用所有客户端的sendMessage函数,而客户端中sendMessage函数就是将信息添加到对应的消息列表中。这样就实现了广播消息的功能了。 

        看到这里,有人是否会有疑问,前面的实现都只用到了集线器对象,而没有用到持久连接对象。其实并不是如此,$.connection这句代码就是使用持久连接对象,当然你也可以在重新OnConnected方法来查看监控客户端的连接情况,更新的代码如下所示:

       1  public class ServerHub : Hub 2     { 3         private static readonly char[] Constant = 4         { 5             0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6             a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, 7             w, x, y, z, 8             A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, 9             W, X, Y, Z10         };11 12         /// <summary>13         /// 供客户端调用的服务器端代码14         /// </summary>15         /// <param name="message"></param>16         public void Send(string message)17         {18             var name = GenerateRandomName(4);19 20             // 调用所有客户端的sendMessage方法21             Clients.All.sendMessage(name, message);22         }23 24         /// <summary>25         /// 客户端连接的时候调用26         /// </summary>27         /// <returns></returns>28         public override Task OnConnected()29         {30             Trace.WriteLine("客户端连接成功");31             return base.OnConnected();32         }33 34         /// <summary>35         /// 产生随机用户名函数36         /// </summary>37         /// <param name="length">用户名长度</param>38         /// <returns></returns>39         public static string GenerateRandomName(int length)40         {41             var newRandom = new System.Text.StringBuilder(62);42             var rd = new Random();43             for (var i = 0; i < length; i++)44             {45                 newRandom.Append(Constant[rd.Next(62)]);46             }47             return newRandom.ToString();48         }49     }

       

        这样在运行页面的时候,将在输出窗口看到“客户端连接成功”字样。运行效果如下图所示:技术分享

      在第二部分介绍的时候说道,在服务端声明的所有Hub信息,都会生成JavaScript输出到客户端,为了验证这一点,可以在Chrome中F12来查看源码就明白了,具体如下图所示:技术分享

       看到上图,你也就明白了为什么Chat.cshtml页面需要引入"signalr/hubs"脚本库了吧。

    •  

      1     <!--引用SignalR库. -->2     <script src=http://www.mamicode.com/"~/Scripts/jquery.signalR-2.2.0.min.js"></script>3      <!--引用自动生成的SignalR 集线器(Hub)脚本.在运行的时候在浏览器的Source下可看到 -->4     <script src="http://www.mamicode.com/~/signalr/hubs"></script>5     

       

  • 五、在桌面程序中如何使用Asp.net SignalR

       上面部分介绍了SignalR在Asp.net MVC 中的实现,这部分将通过一个例子来看看SignalR在WPF或WinForm是如何使用的。其实这部分实现和Asp.net MVC中非常相似,主要不同在于,Asp.net MVC中的SignalR服务器寄宿在IIS中,而在WPF中应用,我们把SignalR寄宿在WPF客户端中。

    下面让我们看看SignalR服务端的实现。

    技术分享
     1 /// <summary> 2         /// 启动SignalR服务,将SignalR服务寄宿在WPF程序中 3         /// </summary> 4         private void StartServer() 5         { 6             try 7             { 8                 SignalR = WebApp.Start(ServerUri);  // 启动SignalR服务 9             }10             catch (TargetInvocationException)11             {12                 WriteToConsole("一个服务已经运行在:" + ServerUri);13                 // Dispatcher回调来设置UI控件状态14                 this.Dispatcher.Invoke(() => ButtonStart.IsEnabled = true);15                 return;16             }17 18             this.Dispatcher.Invoke(() => ButtonStop.IsEnabled = true);19             WriteToConsole("服务已经成功启动,地址为:" + ServerUri);20         }21 22 public class ChatHub : Hub23     {24         public void Send(string name, string message)25         {26             Clients.All.addMessage(name, message);27         }28 29         public override Task OnConnected()30         {31             //32             Application.Current.Dispatcher.Invoke(() =>33                 ((MainWindow)Application.Current.MainWindow).WriteToConsole("客户端连接,连接ID是: " + Context.ConnectionId));34 35             return base.OnConnected();36         }37 38         public override Task OnDisconnected(bool stopCalled)39         {40              Application.Current.Dispatcher.Invoke(() =>41                 ((MainWindow)Application.Current.MainWindow).WriteToConsole("客户端断开连接,连接ID是: " + Context.ConnectionId));42 43             return base.OnDisconnected(true);44         }45     }46 47  public class Startup48     {49         public void Configuration(IAppBuilder app)50         {51             // 有关如何配置应用程序的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkID=31688852             // 允许CORS跨域53             //app.UseCors(CorsOptions.AllowAll);54             app.MapSignalR();55         }56     }
    View Code

    通过上面的代码,我们SignalR服务端的实现就完成了,其实现逻辑与Asp.net MVC的代码类似。

      接下来,让我们看看,WPF客户端是如何连接和与服务器进行通信的。具体客户端的实现如下:

    技术分享
     1 public IHubProxy HubProxy { get; set; } 2         const string ServerUri = "http://localhost:8888/signalr"; 3         public HubConnection Connection { get; set; } 4  5         public MainWindow() 6         { 7             InitializeComponent(); 8  9             // 窗口启动时开始连接服务10             ConnectAsync();11         }12 13         /// <summary>14         /// 发送消息15         /// </summary>16         /// <param name="sender"></param>17         /// <param name="e"></param>18         private void ButtonSend_Click(object sender, RoutedEventArgs e)19         {20             // 通过代理来调用服务端的Send方法21             // 服务端Send方法再调用客户端的AddMessage方法将消息输出到消息框中22             HubProxy.Invoke("Send",  GenerateRandomName(4), TextBoxMessage.Text.Trim());23 24             TextBoxMessage.Text = String.Empty;25             TextBoxMessage.Focus();26         }27 28         private async void ConnectAsync()29         {30             Connection = new HubConnection(ServerUri);31             Connection.Closed += Connection_Closed;32 33             // 创建一个集线器代理对象34             HubProxy = Connection.CreateHubProxy("ChatHub");35 36             // 供服务端调用,将消息输出到消息列表框中37             HubProxy.On<string, string>("AddMessage", (name, message) =>38                  this.Dispatcher.Invoke(() =>39                     RichTextBoxConsole.AppendText(String.Format("{0}: {1}\r", name, message))40                 ));41 42             try43             {44                 await Connection.Start();45             }46             catch (HttpRequestException)47             {48                 // 连接失败49                 return;50             }51 52             // 显示聊天控件53             ChatPanel.Visibility = Visibility.Visible;54             ButtonSend.IsEnabled = true;55             TextBoxMessage.Focus();56             RichTextBoxConsole.AppendText("连上服务:" + ServerUri + "\r");57         }
    View Code

    上面的代码也就是WPF客户端实现的核心代码,主要逻辑为,客户端启动的时候就调用Connection.Start方法与服务器进行连接。然后通过HubProxy代理类来调用集线器中Send方法,而集线器中的Send方法又通过调用客户端的addMessage方法将消息输出到客户端的消息框中进行显示,从而完成消息的推送过程。接下来,让我们看看其运行效果:

  • 技术分享

    从上面的运行效果看出,其效果和Asp.net MVC上的效果是一样的。

    总结

       到这里,本专题的所有内容就结束了,这篇SignalR快速入门也是本人在学习SignalR过程中的一些心得体会,希望可以帮助一些刚接触SignalR的朋友快速入门。本篇主要实现了SignalR的广播消息的功能,可以实现手机端消息推送的功能,接下来一篇将介绍如何使用SignalR实现一对一的聊天。

  • 本文所有源码:链接:http://pan.baidu.com/s/1jHXcW8Q 密码:1otz

Asp.NET MVC 中使用 SignalR 实现推送功能