浅谈Web Api配合SignalR的跨域支持

  最近接手的一个项目中,涉及到一个简单的消息模块,由于之前有简单了解过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 }
View Code

  接下来,在原有的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     }
View Code

  然后写了一个类似于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     }
View Code

  最后就是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     }
View Code

  测试接口就算是基本完成啦~

  再来看下客户端的调用  

 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>
View Code

  本以为可以很轻松的搞定这个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>
View Code

  于是乎,查阅了下官方说明~发现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 }
View Code

  但是,问题同样存在~,这下就纠结了,这是什么个情况!在测试了各种方式之后,问题依旧!于是乎,改用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>

  大功告成啦,再来测试下。

  

  成功跨域!~

 

  

posted @ 2015-03-30 11:51  shoy160  阅读(2982)  评论(2编辑  收藏  举报
The code changes life!