Как поддерживать команду 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>

Это генерирует заголовок REQUEST:

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 Method Not Allowed, но ответ полностью пуст - данные не возвращаются:

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.

Кто-нибудь может помочь? Это должно быть очень распространенная проблема, и я прошу прощения, если здесь был дан ответ. Я искал, но не нашел ответов, которые помогли бы.

ОБНОВЛЕНИЕ IMHO, это проблема на стороне клиента и связана с приведенным выше кодом Ajax JQuery. Я говорю это потому, что Fiddler не показывает никаких заголовков ошибок 405, когда я обращаюсь к mywebapidomain / api / user из веб-браузера. Единственное место, где я могу воспроизвести эту проблему, - это вызов JQuery .ajax (). Кроме того, идентичный вызов Ajax, приведенный выше, отлично работает при запуске на сервере (в том же домене).

Я нашел еще одно сообщение: запрос прототипа AJAX отправляется как ОПЦИИ, а не GET; приводит к ошибке 501, которая кажется связанной, но я безуспешно возился с их предложениями. Судя по всему, JQuery закодирован так, что если запрос Ajax является кросс-доменом (каковым является мой), он добавляет пару заголовков, которые каким-то образом запускают заголовок OPTIONS.

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

Просто кажется, что должно быть лучшее решение, чем изменение основного кода в JQuery ...

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

rwkiii
источник
что вы собираетесь отправить с запросом опций?
Daniel A. White
Мне вообще не нужно отправлять запрос OPTIONS. По какой-то причине это происходит, когда вызов Ajax выполняется кроссдоменом. Итак, как вы можете видеть в Javascript, все, что я делаю, - это указываю GET, но заголовок OPTIONS отправляется по протоколу HTTP. Это «предполетная» проверка.
rwkiii
2
о, вы должны включить cors на своем сервере iis.
Daniel A. White
Это сервер Arvixe - Business Class Pro. Оба сайта размещены на одном физическом сервере и одной учетной записи хостинга. Просто разные имена хостов. Могу ли я включить CORS, не вызывая Arvixe?
rwkiii
я бы позвонил вашему хостинг-провайдеру.
Daniel A. White

Ответы:

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/enpting-cross-origin-requests-in-web-api#enable-cors

Описанная здесь реализация позволяет вам, среди прочего, указать

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

В общем, это работает нормально, но вам нужно убедиться, что вы осведомлены о рисках безопасности, особенно если вы разрешаете запросы с перекрестным происхождением из любого домена. Подумайте очень внимательно, прежде чем позволить это.

В отношении того, какие браузеры поддерживают CORS, Википедия сообщает, что следующие механизмы поддерживают его:

  • Gecko 1.9.1 (FireFox 3.5)
  • WebKit (Safari 4, Chrome 3)
  • MSHTML / Trident 6 (IE10) с частичной поддержкой в ​​IE8 и 9
  • Престо (Опера 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 01
@rwkiii Ссылка действительно является решением, которое включает добавление зависимостей от Web API 5.2.2, но решение было бы более расширяемым, чем взлом, чтобы заставить MVC поддерживать предполетный запрос OPTIONS. Вы также можете просмотреть ответ Доминика, поскольку предполетный запрос может быть результатом заголовков Accept OR Content-Type, которые требуют такого вызова от клиента
Судханшу Мишра
Просто примечание, но если вы установите для типа содержимого значение: '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 только теми клиентами, которых вы хотите, чтобы ваш 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-глагол OPTIONS

 public class BaseApiController : ApiController
  {
    public HttpResponseMessage Options()
    {
      return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
    }
  }
Оливер
источник
@Castaldi это вероятно потому, что предоставленный ответ был нацелен на WebApi 1, в котором не было маршрутизации атрибутов. Для WebApi 2 я бы предложил использовать пакет Microsoft CORS nuget. nuget.org/packages/Microsoft.AspNet.WebApi.Cors
Оливер,
Вы также можете использовать [ApiExplorerSettings (IgnoreApi = true)], чтобы игнорировать конечные точки OPTIONS в Swagger.
Марио Мейреллес
Это сработало для моего приложения 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>

надеюсь это поможет

Chtiwi Malek
источник
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/enpting-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();
        }
    }
yongfa365
источник
1

Я тоже столкнулся с той же проблемой.

Выполните следующий шаг, чтобы решить проблему соответствия (CORS) в браузерах.

Включите REDRock в свое решение со ссылкой на Cors. Включите ссылку WebActivatorEx на решение веб-API.

Затем добавьте файл CorsConfig в папку App_Start веб-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
Что такое редрок? Я сделал поиск в 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>
Рой Фэгон
источник