关于AJAX跨域调用web api问题汇总(二)

来源:互联网 发布:淘宝信誉评级良好 编辑:程序博客网 时间:2024/05/16 11:22

上一篇写到利用Cross进行AJAX跨域,简单的略过了,因为网上有太多的文章说的比我好很多,本人不是很擅长写博客,比较懒。这篇开始说说重点问题。


问题一、跨域ajax提交时,当携带headers头时,请求将报错。无法执行!上代码


 $("#btnPost").click(function () {                $.ajax({                    type: "post",                    url: "http://localhost:8021/Property.Api/Product/FullProducts",                    beforeSend: function (request) {                        request.setRequestHeader("Authorization", "Basic " + localStorage["Token"]);                    },                    data:{Name:"张三"},                    success: function (response) {                        if (response.Code != undefined) {                            document.getElementById("result").innerHTML += "<div>消息为:" + response.Message + "</div>";                            return;                        }                        var tb = $("<table border=1><td>名称</td><td>价格</td><td>排名</td></table>");                        for (var product in response) {                            tb.append("<tr><td>" + response[product].Name + "</td><td>" + response[product].Price + "</td><td>" + response[product].Code + "</td></tr>");                        }                        $("#list").append(tb);                    },                    error: function () {                        document.getElementById("result").innerHTML += "<div>post err</div>";                    }                });            });


重点在于 request.setRequestHeader("Authorization", "Basic " + localStorage["Token"]); 。

应用场景,当做一个web app,使用web api作为服务端的时候,相信和我一样的小白,总会想到如何做身份验证。(最近在研究phonegap)

网上的做法:

     1、输入用户名、密码

     2、登录时 跨域 请求 服务器(web api端),服务器验证帐号密码之后发送一个令牌,返回给客户端

     3、客户端登录之后每次请求数据时,携带令牌给服务器去请求数据,服务器验证令牌是否合法,合法返回请求,不合法返回未授权。常规的说法是header上加入令牌信息,正如上面的代码一样。


 那么、我上面的代码在正常的情况下是否能请求成功呢???答案肯定是,不能!否则我也不会在此废话了。上代码

    public static class tt {        public static int i = 0;    }    public class ProductController : ApiController    {        public List<Product> Products = new List<Product>()        {            new Product(){Name="金丝大环刀",Code="No.1",Price=8888,Created=DateTime.Now},            new Product(){Name="天地阴阳招",Code="No.2",Price=5555,Created=DateTime.Now},            new Product(){Name="乌木剑",Code="No.3",Price=100,Created=DateTime.Now}        };        [AllowAnonymous]        public string GetToken()        {            return "123";        }        public List<Product> FullProducts()        {            tt.i++;//检测是否重复提交            Products[0].Price = tt.i;            return Products;        }    }


其中tt是我用于测试的,待会下一个问题会用到,直接看下面的productcontroller即可,很简单的2个方法,现在当我们请求时看看会发生什么事。




报错了,没错。你看到的是报错了。那么,为什么会报错呢?我们明明已经允许了跨域了的,为什么还会报错????

我们看到上面有个Options的请求,这个具体是干什么呢?

原来在跨域请求时  (  具体经过测试应该是跨域请求post携带header头时,因为我第一个按钮是get请求不会执行这个options请求  )

浏览器会与服务器做一个自检的请求,具体应该是请求服务器的返回头,看是否允许跨域等(详细的请百度)。



那么,问题产生了,我们应该怎么解决?毕竟我们做验证时放在header头是比较优雅的做法,而如果直接把令牌放在每次请求的参数里的话,,客户端调用时就有点工作量了,虽然可以封装ajax请求,达到自动每次携带的目的,但是服务器端去解析和正常参数混在一起的token,感觉始终那么的混乱。。。。所以,我们还是想把令牌放在header里,非常想,优雅的想。。。。。。


解决方案1:客户端关闭options请求,具体的代码为  request.setRequestHeader("X-Requested-With", null]);、此方法为在谷歌上搜索到的方案,本人没有具体测试。

          缺点很明显:你应该尽量少去要求客户(调用者)做更多的事。调用者每次都去加这么一个东西,想想就麻烦,别人干脆不用了。


解决方案2:从web api自身解决。那么应该怎么解决呢?直接贴代码了。

        [HttpOptions]        [HttpPost]        public List<Product> FullProducts()        {            tt.i++;//检测是否重复提交            Products[0].Price = tt.i;            return Products;        }

也就是在我们具体的action上打上 HttpOptions标签,并且如果是Post或get请求,还必须打上Httppost或httpGet标签

   注意:这种情况是Web api 在默认的路由情况下,也就是如下:


  

那么,会发生什么情况呢???????????


看效果图。


你会发现,调用时设置headers成功了。。但是出现了一个严重的问题,就是调用的action实际上被执行了2次,这也就是图1时我写tt一个计数器的原因。

这种错误是无法忍受的,那么如果设置成默认的路由方式,这个问题我我们无法忍受的。。。


那么如何解决这个问题呢??????????难道在每个action里去写这样的代码。

 if( request.Method.ToString().ToLower() == "options")

 {

          return ......

}

else

{

           业务代码...............

}


现在有2个头疼的问题就产生了。

问题1:每个action上必须打上 [HttpOptions]  =》好操蛋

问题2:每个action要去做请求类型判断 。=》稍微有点经验的人,会发现其实这个问题在N多地方可以解决。


现在我们先不解决这个问题,看下如果改变默认的路由,改成mvc的默认路由方式,映射到action之后会出现些什么问题。


出现的问题,和默认路由如出一辙、action方法依然被执行了2次。

        这里声明下:采用默认路由方式的时候其实还有另外一种解决方案是在每个action 加一个  puclic string Options的方法,返回null即可。因为默认路由是根据请求的动作去匹配具体方法的。所以上面说的默认路由的问题,其实是存在于自定义成MVC方式路由产生的。而默认以请求动作的路由时也同样会产生相同问题。


问题总结:


一、采用默认的路由{controller}/{id}时,web api是根据请求动作去匹配action的。

        所产生的问题是需要每个控制器都加上一个 puclic string Options的方法, 

二、采用MVC方式的默认路由{controller}/{action}/{id}时

        所产生的问题是需要在每个具体的action上加上 [HttpOptions]的标签,并且需要做请求判断,否则action里的代码会执行二次。


解决办法:

       如果采用默认的路由{controller}/{id}时,可以自定义一个ApiControllerBase,里面写一个Action 。
        public virtual string Options()
        {
            return "自检";
        }

       如果采用MVC路由方式{controller}/{action}/{id}时,则需要在每个action打上【httpOptions】标签,并做逻辑判断,否则action会被执行2次。


      万金油办法:

                思路:如果路由可以这样干,那么就完美了、当请求为Options请求时,固定路由到某个Action,此Action返回空方法。

                从路由模版去定义貌似实现不了这样一个需求,经过多方考察,终于让我找到了如意的解决方案。

                

    /// <summary>    /// 防止跨域时Options自检引起的重复提交重复提交    /// </summary>    public class OptionsConstraint : IHttpRouteConstraint    {        public bool Match(System.Net.Http.HttpRequestMessage request, IHttpRoute route, string parameterName,            IDictionary<string, object> values, HttpRouteDirection routeDirection)        {            if (request.Method.ToString().ToLower() == "options")            {                if (parameterName != null)                {                    values[parameterName] = "Options";                }            }            return true;        }    }

 在路由的第四个参数加上 

            //默认路由
            config.Routes.MapHttpRoute(
               name: "DefaultApi",
               routeTemplate: "{controller}/{id}",
               defaults: new { id = RouteParameter.Optional },
               constraints: new { action = new OptionsConstraint() }
           );

最后写一个控制器基类。

    public abstract class ApiControllerBase : ApiController
    {
        [HttpOptions]
        public virtual string Options()
        {
            return "自检";
        }
    }

让自己的控制器,继承自ApiControllerBase 



  此方案适合各种路由配置,不管是以api默认路由方式,还是mvc路由到action的方式。


    至此:在客户端跨域调用时,将不再发生Options请求错误,并且也能自定义headers,在headers传递参赛了。


0 0
原创粉丝点击