SignalR 简易聊天室+SignalR如何跨域
十年河东,十年河西,莫欺少年穷
学无止境,精益求精
首先分享下源码:https://download.csdn.net/download/wolongbb/12768075
一个在线聊天室,比本篇博客更齐全
截图如下:
环境:VS2012或以上,Frm版本4.5或以上,我用的是4.5.2
1、在 Visual Studio 中,创建一个 ASP.NET MVC Web 应用程序。
2、右键项目,添加hub类,并命名为:ChatHub【添加后,项目会自动引用SignalR的相关DLL程序集及会在Scripts文件中生成SignalR客户端JS文件】
JS如下:
ChatHub 类的代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Microsoft.AspNet.SignalR; using System.Threading.Tasks; namespace SignalRTx.ChatHubServer { public class ChatHub : Hub { public static List<string> Connectedlst = new List<string>(); /// <summary> /// 当客户端连接时 /// </summary> /// <returns></returns> public override Task OnConnected() { var ConnectionId = Context.ConnectionId; Connectedlst.Add(ConnectionId); return base.OnConnected(); } /// <summary> /// 当客户端断开连接时,例如用户关闭页面,刷新页面 /// </summary> /// <param name="stopCalled"></param> /// <returns></returns> public override Task OnDisconnected(bool stopCalled) { var ConnectionId = Context.ConnectionId; Connectedlst.Remove(ConnectionId); return base.OnDisconnected(stopCalled); } /// <summary> /// 当客户端重连时 /// </summary> /// <returns></returns> public override Task OnReconnected() { var ConnectionId = Context.ConnectionId; if (!Connectedlst.Contains(ConnectionId)) { Connectedlst.Add(ConnectionId); } return base.OnReconnected(); } /// <summary> /// 这是一组广播消息 所有客户端均可收到此消息 /// broadcastMessage 是客户端的一个JS方法,也是回调函数,用于更新客户端 /// send 则是服务端的方法,用于客户端调用 /// </summary> /// <param name="name"></param> /// <param name="message"></param> public void Send(string name, string message) { Clients.All.broadcastMessage(name, message); } } }
在项目中添加命名为Startup的类,如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using Microsoft.AspNet.SignalR; using Microsoft.Owin; using Microsoft.Owin.Cors; using Owin; [assembly: OwinStartup(typeof(SignalRTx.ChatHubServer.Startup))] namespace SignalRTx.ChatHubServer { public class Startup { public void Configuration(IAppBuilder app) { // Any connection or hub wire up and configuration should go here app.MapSignalR(); } } }
在解决方案资源管理器中,右键单击项目,然后选择 "添加 > " HTML 页",如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <title>SignalR Simple Chat</title> <style type="text/css"> .container { background-color: #99CCFF; border: thick solid #808080; padding: 20px; margin: 20px; } </style> </head> <body> <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> <!--Script references. --> <!--Reference the jQuery library. --> <script src="Scripts/jquery-3.4.1.js"></script> <!--Reference the SignalR library. --> <script src="Scripts/jquery.signalR-2.2.2.js"></script> <!--Reference the autogenerated SignalR hub script. --> <script src="signalr/hubs"></script> <!--Add script to update the page and send messages.--> <script type="text/javascript"> $(function () { // Declare a proxy to reference the hub. var chat = $.connection.chatHub; // Create a function that the hub can call to broadcast messages. chat.client.broadcastMessage = function (name, message) { // Html encode display name and message. var encodedName = $('<div />').text(name).html(); var encodedMsg = $('<div />').text(message).html(); // Add the message to the page. $('#discussion').append('<li><strong>' + encodedName + '</strong>: ' + encodedMsg + '</li>'); }; // Get the user name and store it to prepend to messages. $('#displayname').val(prompt('Enter your name:', '')); // Set initial focus to message input box. $('#message').focus(); // Start the connection. $.connection.hub.start().done(function () { $('#sendmessage').click(function () { // Call the Send method on the hub.【服务端方法:send】 chat.server.send($('#displayname').val(), $('#message').val()); // Clear text box and reset focus for next comment. $('#message').val('').focus(); }); }); }); </script> </body> </html>
运行这个HTML页面,效果如下:
聊天内容虽说不那么好吧,但我们对SignalR如何实现跨域还是非常认真的,那么怎么操作才可以跨域通信呢?
要实现SigNalR跨域,首选我们需要安装一个包,执行如下命令:
Install-Package Microsoft.Owin.Cors
然后修改我们的Startup类,如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using Microsoft.AspNet.SignalR; using Microsoft.Owin; using Microsoft.Owin.Cors; using Owin; [assembly: OwinStartup(typeof(SignalRTx.ChatHubServer.Startup))] namespace SignalRTx.ChatHubServer { //public class Startup //{ // public void Configuration(IAppBuilder app) // { // // Any connection or hub wire up and configuration should go here // app.MapSignalR(); // } //} public class Startup { public void Configuration(IAppBuilder app) { // Branch the pipeline here for requests that start with "/signalr" app.Map("/signalr", map => { // Setup the CORS middleware to run before SignalR. // By default this will allow all origins. You can // configure the set of origins and/or http verbs by // providing a cors options with a different policy. map.UseCors(CorsOptions.AllowAll); var hubConfiguration = new HubConfiguration { //You can enable JSONP by uncommenting line below. //JSONP requests are insecure but some older browsers (and some //versions of IE) require JSONP to work cross domain EnableJSONP = true }; // Run the SignalR pipeline. We're not using MapSignalR // since this branch already runs under the "/signalr" // path. map.RunSignalR(hubConfiguration); }); } } }
首先把你的项目复制一个副本出来,放到其他文件夹中,然后修改你复制项目的运行端口号,如下:
最后,客户端动态引用指定URL及端口号的signalR,修改副本HTML如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html> <head> <title>SignalR Simple Chat</title> <style type="text/css"> .container { background-color: #99CCFF; border: thick solid #808080; padding: 20px; margin: 20px; } </style> </head> <body> <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> <!--Script references. --> <!--Reference the jQuery library. --> <script src="Scripts/jquery-3.4.1.js"></script> <!--Reference the SignalR library. --> <script src="Scripts/jquery.signalR-2.2.2.js"></script> <!--Reference the autogenerated SignalR hub script. --> <script src="https://localhost:44330/signalr/hubs"></script> <!--Add script to update the page and send messages.--> <script type="text/javascript"> $(function () { // Declare a proxy to reference the hub. var chat = $.connection.chatHub; chat.connection.url = "https://localhost:44330/signalr"; // Create a function that the hub can call to broadcast messages. chat.client.broadcastMessage = function (name, message) { // Html encode display name and message. var encodedName = $('<div />').text(name).html(); var encodedMsg = $('<div />').text(message).html(); // Add the message to the page. $('#discussion').append('<li><strong>' + encodedName + '</strong>: ' + encodedMsg + '</li>'); }; // Get the user name and store it to prepend to messages. $('#displayname').val(prompt('Enter your name:', '')); // Set initial focus to message input box. $('#message').focus(); // Start the connection. $.connection.hub.start().done(function () { $('#sendmessage').click(function () { // Call the Send method on the hub.【服务端方法:send】 chat.server.send($('#displayname').val(), $('#message').val()); // Clear text box and reset focus for next comment. $('#message').val('').focus(); }); }); }); </script> </body> </html>
变更点:
这样就可以跨域了,下面我们来模拟下跨域的请求,如下:
1、启动并调试服务端项目,端口为:44330,并把chathub所有方法都打上断点,
/2、启动不调试你的副本项目,端口为:44333,我们通过观察断点是否能进入判断跨域请求是否成功
证明跨域访问成功。
聊天内容如下:
最后记录下如何定向发送指定的连接
static readonly IHubContext _myHubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>(); public ActionResult SendMsg() { _myHubContext.Clients.Client(ChatHub.UserLst[0].ConnectionId).broadcastMessage("服务器", "服务器"); return View(); }
客户端有个 broadcastMessage 方法,接收两个参数,如下:
chat.client.broadcastMessage = function (msg1, msg2) { console.log(msg1 + ":" + msg2); };
最后展示下我的成果【含跨域】:
Hub
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Web; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using Newtonsoft.Json.Linq; using Newtonsoft.Json; using Iot.Common; using Iot.Model; using Iot.Dal.Device; using Iot.Factory; using Iot.Model.Pagination; namespace Iot.WebSite.IotHubs { /// <summary> /// 集线器 /// </summary> [HubName("ChatRoomHub")] public class ChatHub : Hub { static string Key = CommEnum.DeviceTypeEnm.YK.ToString(); /// <summary> /// IHubContext /// </summary> static readonly IHubContext _myHubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>(); /// <summary> /// 在线信息 /// </summary> public static List<SigRConnectionModel> UserLst = new List<SigRConnectionModel>(); /// <summary> /// 当客户端连接时 /// </summary> /// <returns></returns> public override Task OnConnected() { return base.OnConnected(); } /// <summary> /// 这是一组广播消息 所有客户端均可收到此消息 /// broadcastMessage 是客户端的一个JS方法,也是回调函数,用于更新客户端 /// send 则是服务端的方法,用于客户端调用 /// </summary> /// <param name="name"></param> /// <param name="message"></param> public void SendBroadcastMsg(string name, string message) { Clients.All.SendBroadcastMsg(name, message); } /// <summary> /// 用户进入时 /// </summary> /// <param name="OpenId"></param> /// <param name="devinceNum"></param> public void userEnter(string OpenId, string devinceNum) { if(!string.IsNullOrEmpty(OpenId) && !string.IsNullOrEmpty(devinceNum)) { var ConnectionId = Context.ConnectionId; SigRConnectionModel u = new SigRConnectionModel() { ConnectionId = ConnectionId, OpenId = OpenId, devinceNum = devinceNum }; UserLst.Add(u); //注意:所有的连接信息 均放在云酷的MongoDb中 var M = SignalrFactory.GetTarget(Key); M.AddSigRConntion(u); _myHubContext.Clients.Client(ConnectionId).OnConnected(JsonConvert.SerializeObject(CommonBaseResponse.SetResponse<SigRConnectionModel>(u, true, "连接成功", 0))); } } /// <summary> /// 当客户端断开连接时,例如用户关闭页面,刷新页面 /// </summary> /// <param name="stopCalled"></param> /// <returns></returns> public override Task OnDisconnected(bool stopCalled) { var ConnectionId = Context.ConnectionId; var ef = UserLst.Where(A => A.ConnectionId == ConnectionId).FirstOrDefault(); if (ef != null) { UserLst.Remove(ef); SigRConnectionModel u = new SigRConnectionModel() { ConnectionId = ConnectionId, OpenId = ef.OpenId, devinceNum = ef.devinceNum }; _myHubContext.Clients.Client(ConnectionId).OnDisconnected(JsonConvert.SerializeObject(CommonBaseResponse.SetResponse<SigRConnectionModel>(u, true, "断开连接", 1))); } return base.OnDisconnected(stopCalled); } /// <summary> /// 当客户端重连时,需要从mongoDb读取连接的信息 /// </summary> /// <returns></returns> public override Task OnReconnected() { var ConnectionId = Context.ConnectionId; var M = SignalrFactory.GetTarget(Key); BasePaginationModel Pagination = new BasePaginationModel() { PageNumber = 1, PageSize = 1 }; var ef = M.GetSigRConntion(ConnectionId, ref Pagination); if (ef != null) { var Connectionef = UserLst.Where(A => A.ConnectionId == ConnectionId).FirstOrDefault(); SigRConnectionModel u = new SigRConnectionModel() { ConnectionId = ConnectionId, OpenId = Connectionef.OpenId, devinceNum = Connectionef.devinceNum }; if (Connectionef == null) { UserLst.Add(u); } _myHubContext.Clients.Client(ConnectionId).OnReconnected(JsonConvert.SerializeObject(CommonBaseResponse.SetResponse<SigRConnectionModel>(u, true, "重连成功", 2))); } return base.OnReconnected(); } } }
Startup
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using Microsoft.AspNet.SignalR; using Microsoft.Owin; using Microsoft.Owin.Cors; using Owin; using System; using System.Collections.Generic; using System.Linq; using System.Web; [assembly: OwinStartup(typeof(Iot.WebSite.IotHubs.Startup))] namespace Iot.WebSite.IotHubs { /// <summary> /// 支持跨域 /// </summary> public class Startup { /// <summary> /// 注册集线器-signalr /// </summary> /// <param name="app"></param> public void Configuration(IAppBuilder app) { app.Map("/signalr", map => { map.UseCors(CorsOptions.AllowAll); var hubConfiguration = new HubConfiguration { EnableJSONP = true }; map.RunSignalR(hubConfiguration); }); } } //public class Startup //{ // /// <summary> // /// 不支持跨域 // /// </summary> // /// <param name="app"></param> // public void Configuration(IAppBuilder app) // { // // Any connection or hub wire up and configuration should go here // app.MapSignalR(); // } //} }
HTML
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> <script src="~/Scripts/jquery-3.4.1.js"></script> <script src="~/Scripts/jquery.signalR-2.2.2.js"></script> <script src="http://api.one5a.com/signalr/hubs"></script> <script type="text/javascript"> $(function () { //是否处于连接状态 var isConnected = false; // Declare a proxy to reference the hub. var chat = $.connection.ChatRoomHub; chat.connection.url = "http://api.one5a.com/signalr"; //连接成功时 触发-仅限于当前连接人收到 chat.client.OnConnected = function (data) { isConnected = true; console.log(data); }; //连接断开时 触发-仅限于当前连接人收到 chat.client.OnDisconnected = function (data) { isConnected = false; console.log(data); }; //重新连接时 触发-仅限于当前连接人收到 chat.client.OnReconnected = function (data) { isConnected = true; console.log(data); }; //广播消息 所有客户端均可以收到 chat.client.SendBroadcastMsg = function (name, message) { console.log(name); console.log(message); }; //开门时响应 chat.client.SigROpenDoor = function (message) { console.log(message); }; //关门时响应 chat.client.SigRCloseDoor = function (message) { console.log(message); }; // Start the connection. $.connection.hub.start().done(function () { chat.server.userEnter("@ViewBag.OpenId","@ViewBag.devinceNum"); }); }); </script> </head> <body> <div> </div> </body> </html>
响应效果
重连时,根据业务需求,需要mongoDB支持。
参考文献:
https://docs.microsoft.com/zh-cn/aspnet/signalr/overview/getting-started/introduction-to-signalr