[廚餘回收] 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 >

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學