Cách hỗ trợ động từ HTTP OPTIONS trong ứng dụng ASP.NET MVC / WebAPI


80

Tôi đã thiết lập ứng dụng web ASP.NET bắt đầu bằng mẫu MVC 4 / Web API. Có vẻ như mọi thứ đang hoạt động rất tốt - không có vấn đề gì mà tôi biết. Tôi đã sử dụng Chrome và Firefox để truy cập trang web. Tôi đã thử nghiệm bằng cách sử dụng Fiddler và tất cả các phản hồi dường như là về tiền.

Vì vậy, bây giờ tôi tiến hành viết một Test.aspx đơn giản để sử dụng API Web mới này. Các phần liên quan của kịch bản:

<script type="text/javascript">
    $(function () {

        $.ajax({
            url: "http://mywebapidomain.com/api/user",
            type: "GET",
            contentType: "json",
            success: function (data) {

                $.each(data, function (index, item) {

                    ....

                    });
                }
                );

            },
            failure: function (result) {
                alert(result.d);
            },

            error: function (XMLHttpRequest, textStatus, errorThrown) {
                alert("An error occurred, please try again. " + textStatus);
            }

        });

    });
</script>

Điều này tạo ra một tiêu đề YÊU CẦU:

OPTIONS http://host.mywebapidomain.com/api/user HTTP/1.1
Host: host.mywebapidomain.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: http://mywebapidomain.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type
Connection: keep-alive

Như vậy, API Web trả về một phương pháp 405 Không được phép.

HTTP/1.1 405 Method Not Allowed
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 30 Sep 2013 13:28:12 GMT
Content-Length: 96

<Error><Message>The requested resource does not support http method 'OPTIONS'.</Message></Error>

Tôi hiểu rằng động từ OPTIONS không được kết nối trong bộ điều khiển API Web theo mặc định ... Vì vậy, tôi đã đặt mã sau trong UserController.cs của mình:

// OPTIONS http-verb handler
public HttpResponseMessage OptionsUser()
{
    var response = new HttpResponseMessage();
    response.StatusCode = HttpStatusCode.OK;
    return response;
}

... và điều này đã loại bỏ lỗi 405 Method Not Allowed, nhưng phản hồi hoàn toàn trống - không có dữ liệu nào được trả lại:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 30 Sep 2013 12:56:21 GMT
Content-Length: 0

Phải có thêm logic ... Tôi không biết làm thế nào để viết mã đúng phương thức Tùy chọn hoặc liệu bộ điều khiển có phải là nơi thích hợp để đặt mã hay không. Kỳ lạ (đối với tôi) là trang Web API phản hồi chính xác khi được xem từ Firefox hoặc Chrome, nhưng lệnh gọi .ajax ở trên lại xảy ra lỗi. Làm cách nào để xử lý việc kiểm tra "preflight" trong mã .ajax? Có lẽ tôi nên giải quyết vấn đề này theo logic .ajax của phía khách hàng? Hoặc, nếu đây là sự cố ở phía máy chủ do không xử lý động từ OPTIONS.

Có ai giúp được không? Đây hẳn là một vấn đề rất phổ biến và tôi xin lỗi nếu nó được giải đáp ở đây. Tôi đã tìm kiếm nhưng không tìm thấy bất kỳ câu trả lời nào hữu ích.

CẬP NHẬT IMHO, đây là vấn đề phía máy khách và liên quan đến mã Ajax JQuery ở trên. Tôi nói điều này vì Fiddler không hiển thị bất kỳ tiêu đề lỗi 405 nào khi tôi truy cập mywebapidomain / api / user từ trình duyệt web. Nơi duy nhất tôi có thể lặp lại sự cố này là từ lệnh gọi .ajax () trong JQuery. Ngoài ra, lệnh gọi Ajax giống hệt ở trên hoạt động tốt khi chạy trên máy chủ (cùng một miền).

Tôi tìm thấy một bài đăng khác: Yêu cầu AJAX nguyên mẫu được gửi dưới dạng TÙY CHỌN chứ không phải GET; dẫn đến lỗi 501 dường như có liên quan, nhưng tôi đã mày mò các đề xuất của họ nhưng không thành công. Rõ ràng, JQuery được mã hóa để nếu một yêu cầu Ajax là miền chéo (của tôi là) thì nó sẽ thêm một vài tiêu đề kích hoạt tiêu đề OPTIONS bằng cách nào đó.

'X-Requested-With': 'XMLHttpRequest',
'X-Prototype-Version': Prototype.Version,

Có vẻ như cần có một giải pháp tốt hơn là sửa đổi mã lõi trong JQuery ...

Câu trả lời được cung cấp bên dưới giả định đây là sự cố phía máy chủ. Có thể, tôi đoán vậy, nhưng tôi nghiêng về phía khách hàng và việc gọi cho một nhà cung cấp dịch vụ lưu trữ sẽ không giúp ích được gì.


bạn sẽ gửi gì với yêu cầu tùy chọn của bạn?
Daniel A. White

Tôi không cần phải gửi yêu cầu TÙY CHỌN. Vì một số lý do, điều này được thực hiện khi một cuộc gọi Ajax được thực hiện chéo miền. Vì vậy, như bạn có thể thấy trong Javascript, tất cả những gì tôi đang làm là chỉ định GET, nhưng tiêu đề OPTIONS được gửi do giao thức HTTP. Đó là một tấm séc "preflight".
rwkiii

2
oh bạn nên bật cors trên máy chủ iis của bạn.
Daniel A. White

Đó là một máy chủ Arvixe - Business Class Pro. Cả hai trang web đều được lưu trữ trên cùng một máy chủ vật lý, cùng một tài khoản lưu trữ. Chỉ khác tên máy chủ. CORS có phải là thứ tôi có thể kích hoạt mà không cần gọi Arvixe không?
rwkiii 30/09/13

tôi sẽ gọi cho nhà cung cấp dịch vụ lưu trữ của bạn.
Daniel A. White

Câu trả lời:


52

Như Daniel A. White đã nói trong nhận xét của mình, yêu cầu TÙY CHỌN rất có thể được khách hàng tạo ra như một phần của yêu cầu JavaScript tên miền chéo. Điều này được thực hiện tự động bởi các trình duyệt tuân thủ Chia sẻ tài nguyên nguồn gốc chéo (CORS). Yêu cầu là một yêu cầu sơ bộ hoặc trước chuyến bay , được đưa ra trước yêu cầu AJAX thực tế để xác định những động từ và tiêu đề yêu cầu nào được hỗ trợ cho CORS. Máy chủ có thể chọn hỗ trợ nó cho không, tất cả hoặc một số động từ HTTP.

Để hoàn thành bức tranh, yêu cầu AJAX có thêm tiêu đề "Nguồn gốc", xác định nơi trang gốc lưu trữ JavaScript được phân phát từ đâu. Máy chủ có thể chọn hỗ trợ yêu cầu từ bất kỳ nguồn gốc nào hoặc chỉ cho một nhóm nguồn gốc đáng tin cậy, đã biết. Cho phép bất kỳ nguồn gốc nào là một rủi ro bảo mật vì nó có thể làm tăng nguy cơ Truy vấn Yêu cầu Trên nhiều trang web (CSRF).

Vì vậy, bạn cần kích hoạt CORS.

Đây là liên kết giải thích cách thực hiện việc này trong ASP.Net Web API

http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api#enable-cors

Việc triển khai được mô tả ở đó cho phép bạn chỉ định, trong số những thứ khác

  • CORS hỗ trợ trên cơ sở mỗi hành động, mỗi bộ điều khiển hoặc toàn cầu
  • Nguồn gốc được hỗ trợ
  • Khi bật bộ điều khiển CORS aa hoặc cấp độ toàn cầu, các động từ HTTP được hỗ trợ
  • Máy chủ có hỗ trợ gửi thông tin xác thực với các yêu cầu gốc chéo hay không

Nói chung, điều này hoạt động tốt, nhưng bạn cần đảm bảo rằng bạn nhận thức được các rủi ro bảo mật, đặc biệt nếu bạn cho phép các yêu cầu nguồn gốc chéo từ bất kỳ miền nào. Hãy suy nghĩ thật kỹ trước khi bạn cho phép điều này.

Về trình duyệt nào hỗ trợ CORS, Wikipedia cho biết các công cụ sau hỗ trợ nó:

  • Gecko 1.9.1 (FireFox 3.5)
  • WebKit (Safari 4, Chrome 3)
  • MSHTML / Trident 6 (IE10) với hỗ trợ một phần trong IE8 và 9
  • Presto (Opera 12)

http://en.wikipedia.org/wiki/Cross-origin_resource_sharing#Browser_support


Chào Mike. Cảm ơn bạn đã liên kết, đây là một liên kết khác cũng tốt: codeguru.com/csharp/.net/net_asp/… - mặc dù cả hai đều chưa khắc phục được sự cố cho tôi. Tôi đã đặt các trang thử nghiệm của mình trên máy chủ ngay bây giờ và điều đó giúp ích cho tôi trong thời gian ngắn. Tôi đã thử cài đặt Microsoft.AspNet.WebApi.Cors nhưng gặp lỗi kỳ lạ là ứng dụng của tôi không có bất kỳ phụ thuộc WebApi nào và do đó, cài đặt đã được khôi phục. Cảm ơn câu trả lời của bạn - Tôi biết nó đúng. +1!
rwkiii

@rwkiii Liên kết thực sự là một giải pháp liên quan đến việc thêm các phụ thuộc vào Web API 5.2.2 nhưng giải pháp này sẽ có thể mở rộng hơn là một cuộc tấn công để buộc MVC hỗ trợ yêu cầu trước chuyến bay OPTIONS. Bạn cũng có thể muốn xem lại câu trả lời của Dominick vì yêu cầu trước chuyến bay có thể là kết quả của tiêu đề Chấp nhận HOẶC Loại nội dung yêu cầu khách hàng gọi như vậy
Sudhanshu Mishra

Chỉ là một ghi chú nhưng nếu bạn đặt loại nội dung thành: 'application / x-www-form-urlencoded', 'multiart / form-data' hoặc 'text / pure' thì yêu cầu được coi là 'đơn giản' và sẽ không đưa ra yêu cầu trước chuyến bay.
mbx-mbx

94

Câu trả lời của Mike Goodwin là tuyệt vời nhưng có vẻ như khi tôi thử, nó nhắm đến MVC5 / WebApi 2.1. Các phụ thuộc cho Microsoft.AspNet.WebApi.Cors không phù hợp với dự án MVC4 của tôi.

Cách đơn giản nhất để kích hoạt CORS trên WebApi với MVC4 là như sau.

Lưu ý rằng tôi đã cho phép tất cả, tôi khuyên bạn nên giới hạn Nguồn gốc chỉ với những khách hàng mà bạn muốn API của mình phục vụ. Cho phép mọi thứ là một rủi ro bảo mật.

Web.config:

<system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Methods" value="GET, PUT, POST, DELETE, HEAD" />
        <add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept" />
      </customHeaders>
    </httpProtocol>
</system.webServer>

BaseApiController.cs:

Chúng tôi làm điều này để cho phép động từ OPTIONS http

 public class BaseApiController : ApiController
  {
    public HttpResponseMessage Options()
    {
      return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
    }
  }

@Castaldi điều này có thể xảy ra vì câu trả lời được cung cấp nhắm vào WebApi 1 không có Định tuyến thuộc tính. Đối với WebApi 2, tôi khuyên bạn nên sử dụng gói nuget CORS của Microsoft. nuget.org/packages/Microsoft.AspNet.WebApi.Cors
Oliver

Bạn cũng có thể sử dụng [ApiExplorerSettings (Bỏ quaApi = true)] để bỏ qua các điểm cuối OPTIONS trên Swagger.
Mário Meyrelles

Điều này đã hoạt động cho Ứng dụng WebApi 2 của tôi. Đặc biệt là phương thức Options () đối với bộ điều khiển cơ sở / có liên quan
lazyList

24

Chỉ cần thêm điều này vào Application_OnBeginRequestphương pháp của bạn (điều này sẽ cho phép hỗ trợ CORS trên toàn cầu cho ứng dụng của bạn) và "xử lý" các yêu cầu trước khi bay:

var res = HttpContext.Current.Response;
var req = HttpContext.Current.Request;
res.AppendHeader("Access-Control-Allow-Origin", req.Headers["Origin"]);
res.AppendHeader("Access-Control-Allow-Credentials", "true");
res.AppendHeader("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
res.AppendHeader("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");

// ==== Respond to the OPTIONS verb =====
if (req.HttpMethod == "OPTIONS")
{
    res.StatusCode = 200;
    res.End();
}

* bảo mật: lưu ý rằng điều này sẽ cho phép các yêu cầu ajax từ bất kỳ đâu đến máy chủ của bạn (thay vào đó bạn chỉ có thể cho phép danh sách Nguồn gốc / url được phân tách bằng dấu phẩy nếu bạn muốn).

Tôi đã sử dụng nguồn gốc ứng dụng khách hiện tại thay *vì vì điều này sẽ cho phép thông tin xác thực => đặt Access-Control-Allow-Credentialsthành true sẽ cho phép quản lý phiên trình duyệt chéo

Ngoài ra, bạn cần bật các động từ xóa và đặt, vá và tùy chọn trong webconfigphần của mình system.webServer, nếu không IIS sẽ chặn chúng:

<handlers>
  <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
  <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
  <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
  <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
  <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>

hi vọng điêu nay co ich


3
Cảm ơn. Chỉ điều này Application_OnBeginRequestđã giúp tôi. Nhưng nếu bạn muốn có thể để có được dữ liệu với sự cho phép quá, bạn cũng nên thêm AuthorizationvàoAccess-Control-Allow-Headers
Ehsan88

18

Sau khi gặp vấn đề tương tự trong dự án Web API 2 (và không thể sử dụng các gói CORS tiêu chuẩn vì những lý do không đáng có ở đây), tôi đã có thể giải quyết vấn đề này bằng cách triển khai DelagatingHandler tùy chỉnh:

public class AllowOptionsHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var response = await base.SendAsync(request, cancellationToken);

        if (request.Method == HttpMethod.Options &&
            response.StatusCode == HttpStatusCode.MethodNotAllowed)
        {
            response = new HttpResponseMessage(HttpStatusCode.OK);
        }

        return response;
    }
}

Đối với cấu hình API Web:

config.MessageHandlers.Add(new AllowOptionsHandler());

Lưu ý rằng tôi cũng đã bật tiêu đề CORS trong Web.config, tương tự như một số câu trả lời khác được đăng ở đây:

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
    <remove name="WebDAVModule" />
  </modules>

  <httpProtocol>
    <customHeaders>
      <add name="Access-Control-Allow-Origin" value="*" />
      <add name="Access-Control-Allow-Headers" value="accept, cache-control, content-type, authorization" />
      <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
    </customHeaders>
  </httpProtocol>

  <handlers>
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <remove name="TRACEVerbHandler" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  </handlers>
</system.webServer>

Lưu ý rằng dự án của tôi không bao gồm MVC, chỉ có Web API 2.


10

Tôi đã khắc phục được lỗi 405 và 404 được đưa ra trên các yêu cầu tùy chọn ajax trước chuyến bay chỉ bằng mã tùy chỉnh trong global.asax

protected void Application_BeginRequest()
    {            
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
        if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
        {
            //These headers are handling the "pre-flight" OPTIONS call sent by the browser
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
            HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
            HttpContext.Current.Response.End();
        }
    }

Tái bút: Xem xét vấn đề bảo mật khi cho phép mọi thứ *.

Tôi đã phải tắt CORS vì nó trả về tiêu đề 'Access-Control-Allow-Origin' chứa nhiều giá trị.

Cũng cần điều này trong web.config:

<handlers>
  <remove name="ExtensionlessUrlHandler-Integrated-4.0"/>
  <remove name="OPTIONSVerbHandler"/>
  <remove name="TRACEVerbHandler"/>
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0"/>
</handlers>

Và app.pool cần được đặt ở chế độ Tích hợp.


8

Tôi có vấn đề này như nhau. Đối với tôi, cách khắc phục là xóa loại nội dung tùy chỉnh khỏi lệnh gọi jQuery AJAX. Các loại nội dung tùy chỉnh kích hoạt yêu cầu trước chuyến bay. Tôi đã tìm thấy cái này:

Trình duyệt có thể bỏ qua yêu cầu preflight nếu các điều kiện sau là đúng:

Các phương thức yêu cầu là GET, HEADhoặc POST,

Ứng dụng này không đặt bất kỳ tiêu đề yêu cầu khác hơn Accept, Accept-Language, Content-Language, Content-Type, hay Last-Event-ID,

Các Content-Typetiêu đề (nếu bộ) là một trong những điều sau đây:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

Từ trang này: http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api (trong "Yêu cầu trước")



2
    protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 405 && Context.Request.HttpMethod == "OPTIONS" )
        {
            Response.Clear();
            Response.StatusCode = 200;
            Response.End();
        }
    }

1

Tôi cũng phải đối mặt với cùng một vấn đề.

Làm theo bước dưới đây để giải quyết vấn đề về tuân thủ (CORS) trong các trình duyệt.

Bao gồm REDRock trong giải pháp của bạn với tham chiếu Cors. Bao gồm tham chiếu WebActivatorEx vào giải pháp API Web.

Sau đó, Thêm tệp CorsConfig vào Thư mục Web API App_Start.

[assembly: PreApplicationStartMethod(typeof(WebApiNamespace.CorsConfig), "PreStart")]

namespace WebApiNamespace
{
    public static class CorsConfig
    {
        public static void PreStart()
        {
            GlobalConfiguration.Configuration.MessageHandlers.Add(new RedRocket.WebApi.Cors.CorsHandler());
        }
    }
}

Với những thay đổi này được thực hiện, tôi đã có thể truy cập webapi trên tất cả các trình duyệt.


4
Redrock là gì? Tôi đã thực hiện tìm kiếm trên Google và tìm kiếm gói Nuget và không có kết quả nào trả về. Một liên kết sẽ tốt.
hofnarwillie

1

Tôi đã gặp vấn đề tương tự và đây là cách tôi khắc phục nó:

Chỉ cần ném cái này vào web.config của bạn:

<system.webServer>
    <modules>
      <remove name="WebDAVModule" />
    </modules>

    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Expose-Headers " value="WWW-Authenticate"/>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS, PUT, PATCH, DELETE" />
        <add name="Access-Control-Allow-Headers" value="accept, authorization, Content-Type" />
        <remove name="X-Powered-By" />
      </customHeaders>
    </httpProtocol>

    <handlers>
      <remove name="WebDAV" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
</system.webServer>

0
//In the Application_OnBeginRequest method in GLOBAL.ASX add the following:-  

var res = HttpContext.Current.Response;  
var req = HttpContext.Current.Request;  
res.AppendHeader("Access-Control-Allow-Origin", "*");  
res.AppendHeader("Access-Control-Allow-Credentials", "true");  
res.AppendHeader("Access-Control-Allow-Headers", "Authorization");  
res.AppendHeader("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");  

    // ==== Respond to the OPTIONS verb =====
    if (req.HttpMethod == "OPTIONS")
    {
        res.StatusCode = 200;
        res.End();
    }

//Remove any entries in the custom headers as this will throw an error that there's to  
//many values in the header.  

<httpProtocol>
    <customHeaders>
    </customHeaders>
</httpProtocol>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.