最近接手的一個項目中,涉及到一個簡單的消息模塊,由於之前有簡單了解過SignalR,所以打算嘗試着摸索摸索~!
首先,通過Nuget管理器添加Microsoft ASP.NET SignalR引用~目前最新版本2.2.0,依賴項目也有點多,什么Microsoft.AspNet.SignalR.JS,Microsoft.AspNet.SignalR.SystemWeb,還有Owin相關的項目,沒法咯,一起統一引用!
添加啟動設置

1 [assembly: OwinStartup(typeof(SignalR.Demo.OwinStartup))] 2 3 namespace SignalR.Demo 4 { 5 public class OwinStartup 6 { 7 public void Configuration(IAppBuilder app) 8 { 9 app.MapSignalR(); 10 } 11 } 12 }
接下來,在原有的Web API項目中Controller里面添加一個支持SignalR的基類~

1 /// <summary> 2 /// SignalR ApiController 3 /// </summary> 4 /// <typeparam name="THub"></typeparam> 5 public abstract class ControllerWithHub<THub> : ApiBase 6 where THub : IHub 7 { 8 private readonly Lazy<IHubContext> _hub = new Lazy<IHubContext>( 9 () => GlobalHost.ConnectionManager.GetHubContext<THub>() 10 ); 11 12 protected IHubContext Hub 13 { 14 get { return _hub.Value; } 15 } 16 }
然后寫了一個類似於Hello World的Hub。

1 [HubName("message")] 2 public class MessageHub : Hub 3 { 4 public void Hello() 5 { 6 Clients.Others.addMessage(string.Format("用戶:{0},進入聊天室!", Context.ConnectionId)); 7 Clients.Caller.addMessage("Welcome!"); 8 } 9 10 public override Task OnConnected() 11 { 12 return base.OnConnected(); 13 } 14 15 public override Task OnDisconnected(bool stopCalled) 16 { 17 return base.OnDisconnected(stopCalled); 18 } 19 20 public override Task OnReconnected() 21 { 22 return base.OnReconnected(); 23 } 24 }
最后就是API接口啦~

1 public class MessageController : ControllerWithHub<MessageHub> 2 { 3 private static readonly List<string> MessageList = new List<string> 4 { 5 "Init Message", 6 "Second Message" 7 }; 8 9 [HttpGet] 10 [Route("~/message/list")] 11 public List<string> List() 12 { 13 lock (MessageList) 14 { 15 return MessageList; 16 } 17 } 18 19 [HttpPost] 20 [Route("~/message/send")] 21 public object Send([FromBody] string message) 22 { 23 var id = "id".Form(""); 24 var msg = "message".Form(string.Empty); 25 lock (MessageList) 26 { 27 MessageList.Add(message); 28 message = string.Format("id:{2}[{0}]:<br/>{1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), msg, 29 id); 30 Hub.Clients.AllExcept(id).addMessage(message); 31 Hub.Clients.Client(id) 32 .addMessage(string.Format("我[{0}]:<br/>{1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), msg)); 33 return new {status = "success"}; 34 } 35 } 36 }
測試接口就算是基本完成啦~
再來看下客戶端的調用

1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"/> 5 <title></title> 6 <link rel="stylesheet" href="../css/amazeui.min.css"/> 7 <style> 8 .msg-list { 9 min-height: 230px; 10 border: solid 1px #eee; 11 width: 1000px; 12 margin: 10px auto; 13 height: 300px; 14 overflow-y: scroll; 15 } 16 </style> 17 </head> 18 <body> 19 <div class="msg-list"> 20 </div> 21 <div class="am-panel" style="width: 1000px;margin: 10px auto;"> 22 <textarea class="am-text-primary" style="width: 500px"></textarea> 23 <button class="btn-message am-btn am-btn-primary">Say</button> 24 </div> 25 <script src="../js/jquery.min.js"></script> 26 <script src="../js/jquery.signalR-2.2.0.min.js"></script> 27 <script type="text/javascript" src="http://localhost/signalr/hubs"></script> 28 <script> 29 (function ($) { 30 var messageHub = $.connection.message; 31 $.connection.hub.logging = true; 32 messageHub.client.addMessage = function (msg) { 33 $(".msg-list").append('<div>' + msg + '</div>'); 34 }; 35 $.connection.hub.start().done(function () { 36 messageHub.server.hello(); 37 $(".btn-message").click(function () { 38 var msg = $(".am-text-primary"); 39 $.ajax({ 40 url: "http://localhost/message/send", 41 data: {id: $.connection.hub.id, message: msg.val()}, 42 type: 'Post', 43 success: function () { 44 msg.val(""); 45 } 46 }); 47 }); 48 }); 49 })(jQuery); 50 </script> 51 </body> 52 </html>
本以為可以很輕松的搞定這個Demo,但是~
第一回合:
SignalR完全沒有加載~
路徑問題,小Case啦~
在頁面JS中手動指定SignalR服務地址
$.connection.hub.url = "http://localhost/signalr";
但是問題並沒有想我想象那么簡單的解決~
由於之前的接口,我已經很粗暴的加入了跨域支持,怎么還會存在跨域的問題呢~
配置如下:

1 <system.webServer> 2 <httpProtocol> 3 <customHeaders> 4 <add name="Access-Control-Allow-Origin" value="*" /> 5 <add name="Access-Control-Allow-Methods" value="GET,POST" /> 6 <add name="Access-Control-Allow-Headers" value="content-type" /> 7 </customHeaders> 8 </httpProtocol> 9 <handlers> 10 <remove name="ExtensionlessUrlHandler-Integrated-4.0" /> 11 <remove name="OPTIONSVerbHandler" /> 12 <remove name="TRACEVerbHandler" /> 13 <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" /> 14 </handlers> 15 </system.webServer>
於是乎,查閱了下官方說明~發現SignalR有着自己的跨域組件Microsoft.Owin.Cors,那就加上吧~在Nuget上找到它,並添加到項目中,稍微修改下啟動設置:

1 [assembly: OwinStartup(typeof(SignalR.Demo.OwinStartup))] 2 3 namespace SignalR.Demo 4 { 5 public class OwinStartup 6 { 7 public void Configuration(IAppBuilder app) 8 { 9 app.Map("/signalr", map => 10 { 11 map.UseCors(CorsOptions.AllowAll); 12 var hubConfiguration = new HubConfiguration 13 { 14 EnableJSONP = true 15 }; 16 map.RunSignalR(hubConfiguration); 17 }); 18 } 19 } 20 }
但是,問題同樣存在~,這下就糾結了,這是什么個情況!在測試了各種方式之后,問題依舊!於是乎,改用Chrome來看下,一下就找到問題出在哪了。
PS:Chrome真不愧是程序員的最愛啊!來張Chrome里面的報錯信息:
這下錯誤信息就很清晰了,api返回的Access-Control-Allow-Origin居然是"http://localhost:63342, *",很明顯是SignalR的跨域組件和我之前粗暴的配置文件沖突了~
那么只有換一種方式了~
1.注釋掉配置文件中system.service里面的跨域支持相關的配置;
2.添加Web API的跨域支持組件System.Web.Cors;
3.在WebApiConfig中開啟跨域支持;
config.EnableCors();
4.在Controller中配置跨域屬性。
[EnableCors("*","*","*")] public class MessageController : ControllerWithHub<MessageHub>
大功告成啦,再來測試下。
成功跨域!~