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

前端之家收集整理的这篇文章主要介绍了关于AJAX跨域调用web api问题汇总(二)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

上一篇写到利用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,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传递参赛了。

原文链接:https://www.f2er.com/ajax/162900.html

猜你在找的Ajax相关文章