• MVC4 Web Api 与 Ajax交互存在的跨域问题总结


    最近项目中要用到mvc4的webapi,其实就是类似webservers wcf的东东( 虽然我都没用过,但是功能和用处还是知道的)作为接口使用,webapi有个好处就是可以把实现IEnumberable接口的数据能根据请求的返回数据类型(xml或者json)自动序列化成这样结构的数据,默认情况下chrome返回的是xml,IE返回的是Json。

    既然是Api肯定是要让其他程序去调用的,webapi只能通过http协议去调用,在我们的项目中是静态页面中通过Ajax去调用的。那么问题就出来了 既然是在静态页面中,肯定是用在其他项目中或者在本地了,这样的话去调用webapi就是跨域访问了,要跨域当然我们会想到jQuery的几个跨域访问的方法,例如:$.get() $.getJSON() $.getScript() $.ajax() 其实这几个在jQuery内部调用的都是$.ajax()。实验了一下才知道用这些方法实现跨域访问必须要在url后面加一个callback=?(?也不能省,jQuery会给我们添上该是什么的,每一次都不一样,为了回调的时候知道是哪个请求发出的。当然在$.ajax()方法中也可指定jsoncallback参数,发出请求后就是?的值)才行,并且返回的时候也要加在返回值得前面。例如webapi的url是www.asss.com/api/values 那么就是www.asss.com/api/values?callback=? 后台返回json格式之前要加callback的值 如:jsoncallback+"("+json+")""    jsoncallback是callback传过去的值 json是json内容   只有这样才能跨域请求成功,否则将会有jQuery报的异常 内容为.....is not called,就是返回来的时候传过去的callback方法没有被执行。造成返回的json字符串也取不到。 虽然说前后都加callback可以跨域成功,但是webapi貌似做个很多处理,要是返回json字符串在chrome浏览器中看到的返回值竟然被加了”“在最外边,在一般的处理程序返回json就不会加引号,这样的话要在前面加上callback也不会被认出来,想到会不会有在response的时候可以处理的可继承的方法,但是没找到 好像有request时候的。

    还好msdn上有个大牛给了个解决方案,链接为 http://code.msdn.microsoft.com/windowsdesktop/Implementing-CORS-support-a677ab5d还有源码下载挺好,大致意思是在跨域的ajax请求中,在request的头部加个Origin什么之类的。具体做法是建个继承  DelegatingHandler 类的子类,重写SendAsync方法

    该类内容如下:

    c#代码
    01 using System;
    02 using System.Collections.Generic;
    03 using System.Linq;
    04 using System.Web;
    05 using System.Net.Http;
    06 using System.Threading;
    07 using System.Threading.Tasks;
    08 using System.Net;
    09  
    10  
    11 namespace Mvc4WebApiTest
    12 {
    13     public class MessageHandler : System.Net.Http.DelegatingHandler
    14     {
    15  
    16         const string Origin = "Origin";
    17         const string AccessControlRequestMethod = "Access-Control-Request-Method";
    18         const string AccessControlRequestHeaders = "Access-Control-Request-Headers";
    19         const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
    20         const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
    21         const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
    22  
    23  
    24         protected override Task<httpresponsemessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    25         {
    26             bool isCorsRequest = request.Headers.Contains(Origin);
    27             bool isPreflightRequest = request.Method == HttpMethod.Options;
    28             if (isCorsRequest)
    29             {
    30                 if (isPreflightRequest)
    31                 {
    32                     return Task.Factory.StartNew<httpresponsemessage>(() =>
    33                     {
    34                         HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
    35                         response.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());
    36  
    37  
    38                         string accessControlRequestMethod = request.Headers.GetValues(AccessControlRequestMethod).FirstOrDefault();
    39                         if (accessControlRequestMethod != null)
    40                         {
    41                             response.Headers.Add(AccessControlAllowMethods, accessControlRequestMethod);
    42                         }
    43  
    44  
    45                         string requestedHeaders = string.Join(", ", request.Headers.GetValues(AccessControlRequestHeaders));
    46                         if (!string.IsNullOrEmpty(requestedHeaders))
    47                         {
    48                             response.Headers.Add(AccessControlAllowHeaders, requestedHeaders);
    49                         }
    50  
    51  
    52                         return response;
    53                     }, cancellationToken);
    54                 }
    55                 else
    56                 {
    57                     return base.SendAsync(request, cancellationToken).ContinueWith<httpresponsemessage>(t =>
    58                     {
    59                         HttpResponseMessage resp = t.Result;
    60                         resp.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());
    61                         return resp;
    62                     });
    63                 }
    64             }
    65             else
    66             {
    67                 return base.SendAsync(request, cancellationToken);
    68             }
    69         }
    70     }
    71 }</httpresponsemessage></httpresponsemessage></httpresponsemessage>

    然后再GlobalApplication_Start的时候注册一下就行了

    GlobalConfiguration.Configuration.MessageHandlers.Add(new MessageHandler());

    测试成功! 前台调用就是普通的ajax请求地址就行了。


    但是页面测试时发现IE浏览器8,9在Jquery ajax与后台通信管道中Handler类的前提下,跨域问题还是存在,Jquery抛出异常“No Transport” js前加上“jQuery.support.cors = true;”后浏览器报错“Error:拒绝访问”,查找资料,有两种办法

    1.设置IE的安全性,当然不可取(这样的话每个用户都要设置)。

    2.IE8 9可通过XDomainRequest对象(http://msdn.microsoft.com/zh-cn/library/dd573303(v=vs.85).aspx)发出跨域请求 但是只能发生get请求。 综合考虑:先继续寻找XDomainRequest发生post等的方法,否则只能在IE8 9种都设置发送get。

     

    javascript代码
    01 if ($.browser.msie && parseInt($.browser.version, 10) >= 6 && window.XDomainRequest) {//IE8 IE9
    02                 var xdr = new XDomainRequest();
    03                     alert(xdr.responseText);
    04                     var data = $.parseJSON(xdr.responseText);
    05                     if (data == null || typeof (data) == 'undefined') {
    06                         data = $.parseJSON(data.firstChild.textContent);
    07                     }
    08                     //需要手动处理json数据
    09                 };
    10                 xdr.onsuccess = function () { };
    11                 xdr.onerror = function (e) {
    12                     alert(xdr.responseText); alert(e);
    13                 }
    14  
    15     xdr.onload = function (e) {
    16  
    17                 xdr.open("post", url);
    18                 xdr.send("dd:dd&ss:ss");
    19             }
    20         else {
    21                 var xhr = jQuery.ajax({
    22                     url: url,
    23                     type: 'post',
    24                     dataType: 'json',
    25                     crossDomain: true,
    26                     data:{SystemName: myModel.systemname(), SystemIp: myModel.systemip(), ContactPerson: myModel.contactperson() },
    27                     success: function (msg) { alert(msg); var jsonmsg = eval("(" + msg + ")"); alert(jsonmsg.state); },
    28                     complete: function () { },
    29                     error: function (a, b, c) { alert("shibai" + a + "-" + b + "-" + c); alert(c); }
    30                 });
    31             }
    32         }

        测试得知XDomainRequest只能发送get和post的请求,并且由于post请求报文头中一定要有“Content-Type:application/x-www-form-urlencoded”  (非提交文件时,有file标签时好像是application/..form-file)因为 XDomainRequest的Content-Type是只读的‘text/plain’所以post不能提交数据,否则就会出错或者后台取不到数据。要想用就只能用get了。  而我最后 放弃web api 改用普通mvc 前台jsonp请求。(jsonp介绍请移步http://www.dnetzj.com/Content/198.html

    jquery的ajax也很简单:

    javascript代码
    1 jQuery.ajax({
    2                 url: url,
    3                 dataType: 'jsonp',
    4                 jsonpCallback: 'callback',//指定后台返回的方法是callback    服务端返回格式:callback('json字符串..')
    5                 data: data,
    6                 success: function(msg){}
    7             });

    以上写的比较乱还是总结一下吧, 如果要在页面跨域访问还是用jsonp方便一点,但因jsonp是以增加script文件,执行完再删除的形式所以jsonp只能发送get请求,所以如果要提交文件就用uploadify插件(flash上传)(参见:回头再说:Uploadify跨域上传原理),要提交的内容长度比较多时就用ajaxcdr.js(flash请求)(参见:jsonp请求url长度过长的替代(ajaxcdr的使用)

  • 相关阅读:
    【python】构造字典类型字典
    【python】序列化和反序列化
    【python】进程
    【python】类中属性方法@property使用
    【python】类中__slots__使用
    【python】类的继承和super关键字
    【python】类的访问限制
    【python】模块作用域
    【python】删除1~100的素数
    【python】函数相关知识
  • 原文地址:https://www.cnblogs.com/huangjihua/p/4125160.html
Copyright © 2020-2023  润新知