Як підтримувати дієслово HTTP OPTIONS у програмі ASP.NET MVC / WebAPI


80

Я налаштував веб-програму ASP.NET, починаючи з шаблону MVC 4 / Web API. Здається, ніби все працює дуже добре - я не знаю проблем. Я використовував Chrome та Firefox для перегляду веб-сайту. Я тестував за допомогою Fiddler, і всі відповіді, здається, залежать від грошей.

Тож тепер я приступаю до написання простого Test.aspx для споживання цього нового веб-API. Відповідні частини сценарію:

<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>

Це генерує заголовок ЗАПИТ:

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

Як і раніше, веб-API повертає метод 405, не дозволений.

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>

Я розумію, що дієслово OPTIONS за замовчуванням не підключено до контролерів веб-API ... Отже, я розмістив такий код у своєму UserController.cs:

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

... і це усунуло помилку 405 Метод не дозволено, але відповідь повністю порожня - дані не повертаються:

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

Повинна бути додаткова логіка ... Я не знаю, як правильно кодувати метод Options, чи контролер навіть є правильним місцем для розміщення коду. Дивно (для мене), що сайт веб-API відповідає належним чином при перегляді з Firefox або Chrome, але виклик вище .ajax виводить помилки. Як мені обробити перевірку "перед вильотом" у коді .ajax? Можливо, я мав би вирішувати цю проблему на логіці .ajax на стороні клієнта? Або, якщо це проблема на стороні сервера через не обробку дієслова OPTIONS.

Хто-небудь може допомогти? Це має бути дуже поширене питання, і я перепрошую, якщо на нього відповіли тут. Я шукав, але не знайшов відповідей, які допомогли.

UPDATE IMHO, це проблема на стороні клієнта і пов’язана з наведеним вище кодом Ajax JQuery. Я кажу це тому, що Fiddler не відображає заголовків помилок 405, коли я отримую доступ до mywebapidomain / api / user із веб-браузера. Єдине місце, де я можу повторити цю проблему, - це виклик JQuery .ajax (). Крім того, ідентичний виклик Ajax вище чудово працює, коли запускається на сервері (той же домен).

Я знайшов інший допис: запит прототипу AJAX надсилається як ОПЦІЇ, а не ОТРИМАЄ; призводить до помилки 501, яка, здається, пов’язана, але я поспілкувався з їх пропозиціями без успіху. Очевидно, JQuery кодується так, що якщо запит Ajax є міждоменним (яким є мій), він додає пару заголовків, які якось запускають заголовок OPTIONS.

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

Просто здається, що має бути краще рішення, ніж модифікація основного коду в JQuery ...

Відповідь, наведена нижче, передбачає, що це проблема сторони сервера. Можливо, я здогадуюсь, але я схиляюся до клієнта, і дзвінок провайдеру хостингу не допоможе.


що ви збираєтеся надіслати з вашим запитом на опції?
Даніель А. Вайт

Мені взагалі не потрібно надсилати запит OPTIONS. З якоїсь причини це робиться, коли виклик Ajax робиться міждоменним. Отже, як ви можете бачити в Javascript, все, що я роблю, це вказати GET, але заголовок OPTIONS надсилається через протокол HTTP. Це перевірка "перед вильотом".
rwkiii

2
о, вам слід увімкнути корди на вашому сервері iis.
Daniel A. White

Це сервер Arvixe - Business Class Pro. Обидва сайти розміщені на одному фізичному сервері, одному обліковому записі хостингу. Просто різні імена хостів. Чи можу я CORS увімкнути, не викликаючи Arvixe?
rwkiii

я би зателефонував до вашого хостинг-провайдера.
Даніель А. Уайт

Відповіді:


52

Як сказав Даніель А. Уайт у своєму коментарі, запит OPTIONS, швидше за все, створюється клієнтом як частина міждоменного запиту JavaScript. Це робиться автоматично браузерами, сумісними з Cross Origin Resource Sharing (CORS). Запит - це попередній або передполітний запит, зроблений перед фактичним запитом AJAX для визначення того, які дієслова та заголовки запиту підтримуються для CORS. Сервер може обрати підтримку для жодного, усіх або деяких дієслів HTTP.

Щоб завершити картинку, запит AJAX має додатковий заголовок "Origin", який визначає, звідки було подано оригінальну сторінку, на якій розміщено JavaScript. Сервер може обрати підтримку запиту з будь-якого джерела або лише для набору відомих надійних джерел. Дозвіл будь-якого походження є ризиком для безпеки, оскільки це може збільшити ризик підроблення запитів між сайтами (CSRF).

Отже, вам потрібно ввімкнути CORS.

Ось посилання, яке пояснює, як це зробити у веб-API ASP.Net

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

Описана там реалізація дозволяє, серед іншого, вказати

  • Підтримка CORS на основі дії, контролера або глобально
  • Підтримувані джерела
  • Під час увімкнення CORS як контролера або глобального рівня підтримуються дієслова HTTP
  • Чи підтримує сервер надсилання облікових даних із запитами про перехресне походження

Загалом це працює нормально, але вам потрібно переконатися, що ви знаєте про ризики безпеки, особливо якщо ви дозволяєте перехресні запити з будь-якого домену. Думайте дуже добре, перш ніж дозволити це.

Що стосується того, які браузери підтримують CORS, Вікіпедія каже, що такі механізми підтримують його:

  • Gecko 1.9.1 (FireFox 3.5)
  • WebKit (Safari 4, Chrome 3)
  • MSHTML / Trident 6 (IE10) з частковою підтримкою в IE8 та 9
  • Престо (Opera 12)

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


Привіт Майк. Дякую за посилання, ось ще одне, що теж добре: codeguru.com/csharp/.net/net_asp/… - хоча жодне з них не вирішило проблему для мене, поки. Наразі я розмістив свої тестові сторінки на сервері, і це допомагає мені на короткий термін. Я спробував встановити Microsoft.AspNet.WebApi.Cors, але отримав дивну помилку, що у мого додатка не було ніяких залежностей WebApi, тому встановлення відкотилося. Дякую за вашу відповідь - я знаю, що це правильно. +1!
rwkiii

@rwkiii Посилання справді є рішенням, яке передбачає додавання залежностей від Web API 5.2.2, але рішення було б більш розширюваним, ніж хак, щоб змусити MVC підтримувати запит OPTIONS до польоту. Ви також можете переглянути відповідь Домініка, оскільки запит перед польотом може бути результатом заголовків Accept OR Content-Type, які вимагають такого дзвінка від клієнта
Sudhanshu Mishra

Просто примітка, але якщо для типу вмісту встановлено значення: 'application / x-www-form-urlencoded', 'multipart / form-data' або 'text / plain', тоді запит вважається "простим" і не видаватиме передполітний запит.
mbx-mbx

94

Відповідь Майка Гудвіна чудова, але, коли я спробував, здавалося, що вона була спрямована на MVC5 / WebApi 2.1. Залежності для Microsoft.AspNet.WebApi.Cors зіграли погано з моїм проектом MVC4.

Найпростіший спосіб увімкнути CORS на WebApi за допомогою MVC4 був наступним.

Зауважте, що я дозволив усі, пропоную обмежити Origin's лише клієнтами, яким ви хочете обслуговувати ваш API. Дозволити все - це ризик для безпеки.

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:

Ми робимо це, щоб дозволити дієслово ОПЦІЇ http

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

@Castaldi це, швидше за все, тому, що надана відповідь була спрямована на WebApi 1, який не мав маршрутизації атрибутів. Для WebApi 2 я б запропонував використовувати пакет CORS nuget від Microsoft. nuget.org/packages/Microsoft.AspNet.WebApi.Cors
Олівер

Ви також можете використовувати [ApiExplorerSettings (IgnoreApi = true)], щоб ігнорувати кінцеві точки OPTIONS на Swagger.
Mário Meyrelles

Це працювало для мого додатка WebApi 2. Особливо метод Options () для відповідного / базового контролера
lazyList

24

Просто додайте це до свого Application_OnBeginRequestметоду (це дозволить глобально підтримувати програму CORS для вашого додатка) і "обробляйте" передпольотні запити:

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();
}

* безпека: майте на увазі, що це дозволить запити ajax з будь-якого місця на ваш сервер (замість цього ви можете дозволити лише розділений комами список джерел / URL-адрес, якщо хочете).

Я використовував поточне походження клієнта замість того, *що це дозволить обліковим даним => встановити Access-Control-Allow-Credentialsзначення true, дозволить керувати сеансами між браузерами

також вам потрібно увімкнути дієслова видалення та розміщення, виправлення та параметрів у вашому webconfigрозділі system.webServer, інакше IIS заблокує їх:

<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>

сподіваюся, це допоможе


3
Дякую. Тільки це Application_OnBeginRequestмені допомогло. Але якщо ви хочете , щоб мати можливість отримати дані з дозволу теж, ви повинні також додати AuthorizationдоAccess-Control-Allow-Headers
Ehsan88

18

Зіткнувшись з тією ж проблемою у проекті Web API 2 (і не маючи можливості використовувати стандартні пакети CORS з причин, які сюди не варто вдаватися), я зміг вирішити цю проблему, застосувавши спеціальний DelagatingHandler:

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;
    }
}

Для конфігурації веб-API:

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

Зверніть увагу, що у мене також включені заголовки CORS у Web.config, подібно до деяких інших відповідей, розміщених тут:

<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>

Зверніть увагу, що мій проект не включає MVC, лише Web API 2.


10

Мені вдалося подолати помилки 405 та 404, що виникають у запитах передполітних опцій Ajax, лише за допомогою користувацького коду в 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();
        }
    }

PS: Враховуйте проблеми безпеки, дозволяючи все *.

Мені довелося відключити CORS, оскільки він повертав заголовок 'Access-Control-Allow-Origin' містить кілька значень.

Це також потрібно в 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>

А app.pool потрібно встановити на інтегрований режим.


8

У мене була така сама проблема. Для мене виправлення полягало в тому, щоб видалити власний тип вмісту із виклику jQuery AJAX. Спеціальні типи вмісту ініціюють запит перед польотом. Я знайшов це:

Браузер може пропустити попередній запит, якщо виконуються наступні умови:

Метод запиту GET, HEADабо POST, і

Додаток не встановлює ніяких заголовків запиту, крім Accept, Accept-Language, Content-Language, Content-Type, або Last-Event-ID, і

Content-TypeЗаголовка (якщо він встановлений) є одним з таких:

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

З цієї сторінки: http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api (у розділі "Запити перед вильотом")



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

1

Я теж стикався з тим же питанням.

Виконайте наступний крок, щоб вирішити проблему відповідності (CORS) у браузерах.

Включіть REDRock у своє рішення із посиланням на Cors. Включіть посилання WebActivatorEx на рішення веб-API.

Потім додайте файл CorsConfig у папку App_Start Web API.

[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());
        }
    }
}

Після цих змін я зміг отримати доступ до webapi у всіх браузерах.


4
Що таке Redrock? Я здійснив пошук у Google та пошук пакетів Nuget, і нічого не повернулось. Посилання було б непогано.
hofnarwillie

1

У мене була така сама проблема, і ось як я її виправив:

Просто додайте це у свій web.config:

<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>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.