[厨余回收] jQuery $.ajax 执行跨域调用时,收到错误消息“…Response to preflight request doesn’t pass access control check…”。

前些日子我们有一个需求,需要将一个 Web Api 开放给另一个也是在 intranet 但是不同 domain 的网页调用,在拜完 Google 大神后写了一个跨域调用的 Sample,但是过程当中却遇到了一些问题…


先来讲讲怎么使用 ASP.NET MVC 5 提供可跨域调用的 Web Api。

新增允许跨域调用的 ActionFilter

public class AllowCORSAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.RequestContext.HttpContext.Response.AddHeader("Access-Control-Allow-Origin", "*");
        filterContext.RequestContext.HttpContext.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type");
        filterContext.RequestContext.HttpContext.Response.AddHeader("Access-Control-Allow-Methods", "POST");

        base.OnActionExecuting(filterContext);
    }
}

在允许跨域调用的 Action 给予 Attribute

[HttpPost]
[AllowCORS]
public ActionResult Hello(string keyword)
{
    return Json(new { Hello = "Hi" });
}

使用 jQuery $.ajax 调用允许跨域调用的 Web Api

$("#testButton").click(function () {
    $.ajax({
        type: 'POST',
        url: "http://localhost:20870/CORS/Hello",
        data: "{ "keyword": "jack" }",
        contentType: 'application/json',
        success: function (result) {
            alert(JSON.stringify(result));
        },
        error: function (xhr, textStatus, thrownError) {
            alert(textStatus);
        }
    });
});

上面这段语法乍看之下好像没什么问题,data 是 JSON 格式,contentType 是 application/json,可是却无法调用成功,收到下面这串消息。

原来只要我们的 HTTP Request 符合以下其中一种条件时,默认就会以 OPTIONS 的方式先送 preflight request 出去,以确认后续真实请求是否可安全送出。

  • It uses methods other than GET, HEAD or POST. Also, if POST is used to send request data with a Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain, e.g. if the POST request sends an XML payload to the server using application/xml or text/xml, then the request is preflighted.
  • It sets custom headers in the request (e.g. the request uses a header such as X-PINGOTHER)

因为刚刚那段异步 POST request 语法的 Content-Type 是 application/json,符合 Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain 的条件,所以我们的 request 就被当做是 preflight request 先以 OPTIONS 方式给送了出去。

避免跨域调用多送 preflight request

其实多送 preflight request 也没关系,你只要准备一个可以接收 OPTIONS request 的 Web Api 来处理它就行了,不过这不是我想要的,所以只要我们的 request 不要去踩到会发送 preflight request 的红线就行了,来改一下语法。

$("#testButton").click(function () {
    $.ajax({
        type: 'POST',
        url: "http://localhost:20870/CORS/Hello",
        data: { keyword: "jack" },
        success: function (result) {
            alert(JSON.stringify(result));
        },
        error: function (xhr, textStatus, thrownError) {
            alert(textStatus);
        }
    });
});

这样就可以跨域调用成功了。

参考数据

 < Source Code >

  • CORS in ASP .NET MVC5
  • HTTP access control (CORS)