你的位置:首页 > ASP.net教程

[ASP.net教程]http与websocket两种协议下的跨域基于ASP.NET MVC


这段时间,项目涉及到移动端,这就不可避免的涉及到了跨域的问题。这是本人第一次接触跨域,有些地方的配置是有点麻烦,导致一开始的不顺。

至于websocket具体是什么意义,用途如何:请百度。

简单说就是建立一个基于互联网的实时通信。

在这里整理下这些内容,方便日后回顾。

一:介绍了WebSocket下的基于SignalR的跨域与不跨域例子

二:简单介绍了Http下的跨域问题

 

Ⅰ.WebSocket下的跨域

如果使用原生的方法来开发WebSocket应用,还是比较复杂的,不过好在Asp.net给我们提供了一个框架——

SignalR:

  微软支持的运行在.NET平台上集客户端与服务器于一体的库。简单来说就是给我们提供了服务端的类库加上前端的JS库。

首先抛开跨域的问题,先写一个SignalR不跨域的例子:

1、NuGet来获得我们所需要的程序集:

SignalR:我们的框架

 

2、新建个继承于Hub的类

 1 /// <summary> 2   /// 对应不跨域的方法 3   /// </summary> 4   public class ChatHub : Hub 5   { 6     public void Send(string message) 7     { 8       //接收数据,再传输到指定或全部的客户端动态方法 9       Clients.All.addNewMessageToPage(message);10     }11 }

3、新建个Startup类:

只需添加一句话:app.MapSignalR();

具体的完整代码在下面的Startup里都有。

 

4、这样我们的服务端代码就完成了,接下来就是客户端代码:

 1 $(function() {  2  3 var chat = $.connection.chat; 5  $.connection.hub.start(); 9  chat.addMessage = function(message){11 alert(message);13 }14 15 $("#btn").click(function(){16 17   chat.send("给服务端发送信息");18 19 }); 20 21 });

 

5、好了,现在来分析下客户端代码:

前面两行就是基本的猜也能猜到的获取实例,然后开启这个连接。(相当于启动线程)。第三行定义了一个函数,这个函数是与服务端的ChatHub类里的动态方法addNewMessageToPage对应的回调函数。服务端一执行动态方法,对应的客户端(这个由服务端发送时的选择有关,这里选择就是All,也就是所有的客户端)就执行对应的回调函数。

现在知道了动态方法跟回调函数是一一对应的,服务端触发动态方法,客户端执行回调函数。那么动态方法什么时候执行呢,显而易见,是客户端发送请求时,也就是这里“调用”服务端定义的Send方法时,触发了动态方法。(有点啰嗦了,为了保证人人看懂)。这样一来,最后这句 chat.send("给服务端发送信息");就很容易理解了。就是请求。

 

二、跨域(一)

先来从NuGet获取我们需要跨域所需的程序集:

 

有了前面的基础,对于SignalR也应该有了个基础的印象了,下面就来讲讲如何用SignalR实现跨域通信。

SignalR的实现机制与.NET WCF 或Remoting是相似的,都是使用远程代理来实现,在具体使用上,有两种不同目的的接口:PersistentConnection和Hub,其中PersistentConnection实现了长时间的轮询,Hub用来解决实时信息的交换问题。

先来了解一下基于PersistentConnection接口的跨域。

我们的跨域一:

1、注释掉我们Startup类中的唯一一行代码:app.MapSignalR();

然后换上我们跨域的代码:

1 //这个参数"/echo",是我们自己定义的一个路由,与客户端创建SignalR的实例时对应。2       app.Map("/echo",3       map =>4       {5         map.UseCors(CorsOptions.AllowAll);6         map.RunSignalR<EchoConnection>();7       }8     );

2、新建一个类,继承于我们的接口PersistentConnection

通信的本质就是建立一个客户端与服务端的一个连接,SignalR就是我们帮助我们建立这个连接的“中间件”

这个类中就实现了接口中定义的一系列方法,如连接建立,断开连接,收到信息。这样一来,我们的chatHub类实际上就已经不起作用了,通信的方法都可以写在这个EchoConnection类中。

 1 public class EchoConnection:PersistentConnection 2   { 3     /// <summary> 4     /// 当前连接数 5     /// </summary> 6     private static int _connections = 0; 7     /// <summary> 8     /// 连接建立时执行 9     /// </summary>10     /// <param name="request"></param>11     /// <param name="connectionId"></param>12     /// <returns></returns>13     protected override async Task OnConnected(IRequest request, string connectionId)14     {15       Interlocked.Increment(ref _connections);16       await Connection.Send(connectionId, "Hi, " + connectionId + "!");17       await Connection.Broadcast("新连接 " + connectionId + "开启. 当前连接数: " + _connections);18       19     }20     /// <summary>21     /// 连接关闭时执行22     /// </summary>23     /// <param name="request"></param>24     /// <param name="connectionId"></param>25     /// <returns></returns>26     protected Task OnDisconnected(IRequest request, string connectionId)27     {28       Interlocked.Decrement(ref _connections);29       return Connection.Broadcast(connectionId + " 连接关闭. 当前连接数: " + _connections);30     }31     /// <summary>32     /// 服务器接收到前台发送的消息时执行 发送请求 connection.send("信息");33     /// </summary>34     /// <param name="request"></param>35     /// <param name="connectionId"></param>36     /// <param name="data"></param>37     /// <returns></returns>38     protected override Task OnReceived(IRequest request, string connectionId, string data)39     {40       var message = connectionId + ">> " + data;41       return Connection.Broadcast(message);42     }43 44 }

View Code

 

3、接下来就是客户端代码:

同样,第一步,获取实例,建立连接

var connection = $.connection("http://localhost:23013/echo");//echo就是我们之前在Startup类中定义的路由。

connection.start();

建立连接后,就会触发服务端的OnConnected事件,然后这个事件中又有触发两个方法:Broadcast,Send。这个就相当于我们不跨域时自己定义的动态方法,so,我们就应该想到客户端应有对应的回调函数:Broadcast跟Send。但是!由于这不是我们自定义的,所以,名字就不是我们想的这样啦~~对于接收信息,我们直接调用connection.received(function (data){})来接收,对于发送信息到服务端也不需要我们手动来先在服务端定义方法如不跨域时在ChatHub中定义的send方法,直接connection(实例名).send()就可以发消息到服务端。而在服务端,是由OnReceived来处理。具体的代码同样在本文的最后。

 跨域(一)小结:

  

 1 /// <summary> 2   /// 对应跨域的第一种方法  3   /// 1、前台直接用connection(实例).send()就可以发消息到服务端,用connection.received(function (data) { 4   ///                                       $("body").append(data + "<br />"); 5   ///                                      });就能接收服务端放过来的消息 6   /// 2、后台Connection.Send(connectionId, "消息");可发送消息给指定的客户端 7   ///  用Connection.Broadcast("消息");可发送给所有的客户端 8   ///  用protected override Task OnReceived(IRequest request, string connectionId, string data) 9   ///   {10   ///     var message = connectionId + ">> " + data;11   ///     return Connection.Broadcast(message);12   ///   }接收客户端发送来的消息,进行处理13   /// </summary>

 

三、跨域(二)

 相比PersistentConnection这种轮询机制,基于Hub利用js动态载入执行方法才是我们所说的websocket技术。

Let’s start

1、有的朋友应该知道我们的第一步都是为前端可以创建实例准备的了

再一次修改我们的Startup类,之前的注释掉:

 

 1 app.Map("/signalr", map => 2       { 3         map.UseCors(CorsOptions.AllowAll); 4         var hubConfiguration = new HubConfiguration 5         { 6           EnableJSONP = true//跨域的关键语句 7         }; 8         map.RunSignalR(hubConfiguration); 9       });10       app.MapSignalR();

 

上面的/signalR即我们这次设置的路由。一样,供客户端匹配

 

1、重新建立我们的MyHub类,跟最开始不跨域的步骤一致,新建类,继承于Hub。然后实现接口,里面也有连接、断开连接、重连事件。以及我们自定义的方法也都写在这里:

 

 1 /// <summary> 2   /// 对应跨域的第二种方法 3   /// public IGroupManager Groups { get; set; } hub类中还有个组属性,尚待开发,待看文档,再写demo 4   /// </summary> 5   public class MyHub : Hub 6   { 7     /// <summary> 8     /// 连接前执行 9     /// </summary>10     /// <returns></returns>11     public override System.Threading.Tasks.Task OnConnected()12     {13       //Clients.All.sayHello("连接成功");14       Clients.Caller.sayHello("连接成功");//当前请求的用户15       return base.OnConnected();16     }17 18     /// <summary>19     /// 断开链接时执行20     /// </summary>21     /// <param name="stopCalled"></param>22     /// <returns></returns>23     public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)24     {25       return base.OnDisconnected(stopCalled);26     }27 28     /// <summary>29     /// 重新建立连接 如服务器重启的时候,或者前台获取超时仍在等待,重连上时30     /// </summary>31     /// <returns></returns>32     public override System.Threading.Tasks.Task OnReconnected()33     {34       return base.OnReconnected();35     }36 37     public void Hello(string name)//方法名称首字母大小写都与前台匹配上 前台首字母必须小写38     {39       //动态方法,与前台的回调函数名称一致40       Clients.All.sayHello2("第二次");41       Clients.All.sayHello3(Context.ConnectionId);//第三个回调函数,返回链接的ConnectionId42     }43   }

View Code

 

 3、我们的客户端

第一步:获取实例,建立连接

var chat = $.connection.myHub; //获取服务端实例 首字母小写,不是跟服务端一致的MyHubchat.connection.url = "http://localhost:23013/signalr";测试项目的地址$.connection.hub.start();

第二步、接下来就是跟服务端打个招呼

Chat.server.hello(“hello”);//对应的方法中有两个动态方法,sayHello2,sayHello3第三步、接下来就是我们的回调函数,对应的回调函数就可以有两个,与上面对应。  chat.client.sayHello2 = function(msg) {          alert(msg);  };chat.client.sayHello3 = function(msg) {          alert(msg);  };

 

WebSocket小结:基于Hub接口的跨域方法的可扩展性还是很强的,这里有几个本人总结的注意点(针对Hub跨域):

1、回调函数 函数名要匹配,参数可以不匹配,后台传过来一个就执行一个,即若后台同时触发了两个同名的sayHello 依次执行

 2、一定要有回调函数,不然不算连接成功,可以不调用服务器端的方法。若只是调用服务器端方法,没写回调函数,依然不算连接成功

3、在连接成功的情况下,后台先执行OnConnected事件,再执行前台调用的某个方法

4、用回调函数来判断是否真的连接成功,$.connection.hub.start().done里直接输出连接成功,是假成功。

5、所以同样,断开连接是否成功也应用回调函数来判断,这个回调函数对应后台代码应在OnDisconnected事件里

6、第五点失败,sayGoodBye是在执行完这个事件(OnDisconnected)后才传输到前台,而事件中执行完已经把链接断开了,前台是接收不到的。

 

Ⅱ、HTTP下的跨域

相对于websocket协议的跨域,http应该是简单的,网上也有很多资料。这里就当笔者自己的笔记吧。

http下的跨域指异步的传输,也就是说form表单提交这种非异步方式是不存在跨域问题的。网上最多的异步跨域是jsonp方式,但jsonp不可进行post传输,只能进行get。我比较喜欢下面这种简洁的方式:

$.ajax({    type:"GET",                                      
url:"http://localhost:6874/Admin/Login/IsLegal", //跨域URL dataType:"json", data:{ par1:”参数1”, par2:”参数2” }, success:function (result){ if(result.id == "123"){ $("#div1").html("验证成功,可进行跳转"); }else{ $("#div1").html(result.name); } }, error:function (// 调用本次AJAX请求时传递的options参数 }});

服务端代码:

1 public ActionResult IsLegal(string par1,string par2)2   {3       var str = new { id = "123", name = "joey" };4 5       httpcontext.response.appendheader("access-control-allow-origin", "*");6 7       return json(str, jsonrequestbehavior.allowget);8   }

简单实用,同时可以进行post传输。

最后是之前上文一直提到的“本文最后”

 1 #region 不跨域的方法 2       //app.MapSignalR(); 3       #endregion 4  5       #region 跨域的第一种方法 6       //这个参数"/echo",是我们自己定义的一个路由,与客户端创建SignalR的实例时对应。 7       app.Map("/echo", 8       map => 9       { 10         map.UseCors(CorsOptions.AllowAll); 11         map.RunSignalR<EchoConnection>(); 12       } 13     ); 14       #endregion 15  16       #region 第一种方法前端调用代码 17       //  <script src="js/jquery-1.8.2.js"></script> 18       //  <script src="js/jquery.signalR-2.2.0.min.js"></script> 19       //  <script type="text/javascript"> 20       //  $(function () { 21       //    var connection = $.connection("http://localhost:23013/echo");//对应的服务器端地址 22       //    //var connection = $.connection("http://192.168.137.1/q"); 23       //    connection.logging = true; 24       //    connection.received(function (data) { 25       //      $("body").append(data + "<br />"); 26       //    }); 27       //    connection.error(function (err) { 28       //      alert("存在一个错误. \n" + 29       //        "Error: " + err.message); 30       //    }); 31       //    connection.start().done(function () { 32       //      $("#send").click(function () { 33       //        connection.send($("#text").val()); 34       //        $("#text").val("").focus(); 35       //      }); 36       //    }); 37       //  }); 38       //</script> 39       #endregion 40  41       #region 跨域的第二种方法 42       //app.Map("/signalr", map => 43       //{ 44       //  map.UseCors(CorsOptions.AllowAll); 45       //  var hubConfiguration = new HubConfiguration 46       //  { 47       //    EnableJSONP = true//跨域的关键语句 48       //  }; 49       //  map.RunSignalR(hubConfiguration); 50       //}); 51       //app.MapSignalR(); 52  53       #endregion 54  55       #region 第二种方法前台代码 56       //      <script src="js/jquery-1.8.2.js"></script> 57       //      <script src="js/jquery.signalR-2.2.0.min.js"></script> 58       //      <script src="http://localhost:23013/signalr/js" type="text/javascript" charset="utf-8"></script>地址是服务器端地址加上/设置的路由/js 59       //      <script type="text/javascript"> 60       //      var chat = $.connection.myHub; //获取服务端实例 首字母小写,不是跟服务端一致的MyHub 61       //console.log(chat); 62       //chat.connection.url = "http://localhost:23013/signalr";测试项目的地址 63       //      chat.connection.url = "http://localhost:6874/joey"; 64       //1、回调函数 函数名要匹配,参数可以不匹配,后台传过来一个就执行一个,即若后台同时触发了两个同名的sayHello 65       //依次执行 66       //2、一定要有回调函数,不然不算连接成功,可以不调用服务器端的方法。若只是调用服务器端方法,没写回调函数, 67       //依然不算连接成功 68       //3、在连接成功的情况下,后台先执行OnConnected事件,再执行前台调用的某个方法 69       //4、用回调函数来判断是否真的连接成功,$.connection.hub.start().done里直接输出连接成功,是假成功。 70       //5、所以同样,断开连接是否成功也应用回调函数来判断,这个回调函数对应后台代码应在OnDisconnected事件里 71       //6、第五点失败,sayGoodBye是在执行完这个事件(OnDisconnected)后才传输到前台,而事件中执行完已经把链接断开了,前台是接收不到的 72       //chat.client.sayHello = function(connectionCode) { 73       //        if(connectionCode == 1) 74       //        { 75       //          $("#IsConnSuc").val("1");//给隐藏字段赋值,1表示连接成功 76       //          alert("连接成功"); 77       //        } 78       //        else 79       //        { 80       //          alert("连接失败"); 81       //        } 82       //      }; 83       //-----------注释掉的--------------------------------------------------------------------- 84       //      chat.client.sayGoodBye = function(connectionCode) { 85       //        if(connectionCode == 1) 86       //        { 87       //          alert("断开连接成功"); 88       //        } 89       //        else 90       //        { 91       //          alert("断开连接失败"); 92       //        } 93       //      }; 94       //      //建立连接 95       //      $.connection.hub.start().done(function() { 96       //        // Call the Send method on the hub. 97       //        chat.server.hello("fd"); //调用服务器端定义的方法 方法名首字母小写,后台对应的方法首字母大小写都能匹配上 98       //      }); 99       //-----------注释掉的---------------------------------------------------------------------100       //  $(function(){101       //    $("#start").click(function(){102       //      //建立连接103       //      $.connection.hub.start();104       //    });105 106       //    $("#stop").click(function() {107       //      if($("#IsConnSuc").val() == "1"){108       //        //有连接时,才执行断开连接操作109       //        $.connection.hub.stop();110       //        $("#IsConnSuc").val("0");111       //        alert("断开连接成功");112       //      }113 114       //    });115 116       //  });117 118       //</script>119       #endregion

View Code

 

如果您认为这篇文章还不错或者有所收获,可以点击右下角的【推荐】按钮,因为你的支持是我继续写作,分享的最大动力!
作者:玛丽的竹子
来源:http://www.cnblogs.com/joeymary/
声明:本博客原创文字只代表本人工作中在某一时间内总结的观点或结论。非商业,未授权,贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文连接。