Jak obsługiwać czasownik HTTP OPTIONS w aplikacji ASP.NET MVC / WebAPI

80

Skonfigurowałem aplikację internetową ASP.NET, zaczynając od szablonu MVC 4 / Web API. Wygląda na to, że wszystko działa naprawdę dobrze - żadnych problemów, których jestem świadomy. Użyłem przeglądarki Chrome i Firefox do przeglądania witryny. Przetestowałem za pomocą programu Fiddler i wszystkie odpowiedzi wydają się dotyczyć pieniędzy.

Więc teraz przystępuję do pisania prostego pliku Test.aspx, aby korzystać z tego nowego internetowego interfejsu API. Odpowiednie części scenariusza:

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

To generuje nagłówek 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

W obecnej postaci interfejs API sieci Web zwraca metodę 405 niedozwoloną.

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>

Rozumiem, że zlecenie OPTIONS nie jest domyślnie podłączone do kontrolerów Web API ... Więc umieściłem następujący kod w moim UserController.cs:

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

... i to wyeliminowało błąd 405 Method Not allowed, ale odpowiedź jest całkowicie pusta - żadne dane nie są zwracane:

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

Musi być dodatkowa logika ... Nie wiem, jak poprawnie zakodować metodę Options lub czy kontroler jest nawet odpowiednim miejscem do umieszczenia kodu. Dziwne (dla mnie), że witryna interfejsu API sieci Web reaguje prawidłowo, gdy jest wyświetlana w przeglądarce Firefox lub Chrome, ale wywołanie .ajax powyżej zawiera błędy. Jak obsłużyć sprawdzanie „preflight” w kodzie .ajax? Może powinienem zająć się tym problemem w logice .ajax po stronie klienta? Lub, jeśli jest to problem po stronie serwera z powodu nieobsługiwania czasownika OPTIONS.

Czy ktoś może pomóc? To musi być bardzo częsty problem i przepraszam, jeśli został tutaj rozwiązany. Szukałem, ale nie znalazłem żadnych odpowiedzi, które pomogły.

UPDATE IMHO, jest to problem po stronie klienta i ma związek z powyższym kodem Ajax JQuery. Mówię to, ponieważ Fiddler nie wyświetla żadnych nagłówków błędów 405, gdy uzyskuję dostęp do mywebapidomain / api / user z przeglądarki internetowej. Jedynym miejscem, w którym mogę powielić ten problem, jest wywołanie JQuery .ajax (). Ponadto identyczne wywołanie Ajax powyżej działa dobrze, gdy jest uruchomione na serwerze (w tej samej domenie).

Znalazłem inny post: Prototypowe żądanie AJAX jest wysyłane jako OPCJE, a nie GET; skutkuje błędem 501, który wydaje się być powiązany, ale bezskutecznie majstrowałem przy ich sugestiach. Najwyraźniej JQuery jest zakodowany w taki sposób, że jeśli żądanie Ajax jest międzydomenowe (które jest moim), dodaje kilka nagłówków, które w jakiś sposób wyzwalają nagłówek OPTIONS.

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

Po prostu wydaje się, że powinno być dostępne lepsze rozwiązanie niż modyfikacja kodu rdzenia w JQuery ...

Poniższa odpowiedź zakłada, że ​​jest to problem po stronie serwera. Może, tak myślę, ale skłaniam się ku klientowi i dzwonienie do dostawcy hostingu nie pomoże.

rwkiii
źródło
co zamierzasz wysłać z prośbą o opcje?
Daniel A. White
W ogóle nie muszę wysyłać żądania OPTIONS. Z jakiegoś powodu dzieje się to, gdy połączenie Ajax jest wykonywane między domenami. Tak więc, jak widać w JavaScript, wszystko, co robię, to określanie GET, ale nagłówek OPTIONS jest wysyłany z powodu protokołu HTTP. To jest kontrola wstępna.
rwkiii
2
och, powinieneś włączyć cors na swoim serwerze iis.
Daniel A. White
To serwer Arvixe - Business Class Pro. Obie witryny są hostowane na tym samym serwerze fizycznym, na tym samym koncie hostingowym. Tylko różne nazwy hostów. Czy CORS jest czymś, co mogę włączyć bez dzwonienia do Arvixe?
rwkiii
zadzwoniłbym do twojego dostawcy usług hostingowych.
Daniel A. White

Odpowiedzi:

52

Jak powiedział Daniel A. White w swoim komentarzu, żądanie OPTIONS jest najprawdopodobniej tworzone przez klienta jako część międzydomenowego żądania JavaScript. Odbywa się to automatycznie przez przeglądarki zgodne ze standardem Cross Origin Resource Sharing (CORS). Wniosek ma charakter wstępny lub przed lotem , wysłanym przed rzeczywistym żądaniem AJAX w celu określenia, które czasowniki i nagłówki żądania są obsługiwane dla CORS. Serwer może zdecydować się na obsługę go dla żadnego, wszystkich lub niektórych czasowników HTTP.

Aby uzupełnić obraz, żądanie AJAX ma dodatkowy nagłówek „Origin”, który wskazuje, skąd pochodzi oryginalna strona, na której znajduje się JavaScript. Serwer może obsługiwać żądania z dowolnego źródła lub tylko dla zestawu znanych, zaufanych źródeł. Zezwolenie na dowolne źródło jest zagrożeniem dla bezpieczeństwa, ponieważ może zwiększyć ryzyko fałszowania żądań między lokacjami (CSRF).

Musisz więc włączyć CORS.

Oto łącze, które wyjaśnia, jak to zrobić w interfejsie ASP.Net Web API

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

Opisana tam implementacja pozwala określić między innymi

  • Obsługa CORS na zasadzie per-action, per-controller lub global
  • Obsługiwane źródła
  • Podczas włączania kontrolera CORS aa lub poziomu globalnego obsługiwane są polecenia HTTP
  • Czy serwer obsługuje wysyłanie poświadczeń z żądaniami między źródłami

Ogólnie działa to dobrze, ale musisz mieć świadomość zagrożeń bezpieczeństwa, zwłaszcza jeśli zezwalasz na żądania z różnych źródeł z dowolnej domeny. Pomyśl bardzo uważnie, zanim na to pozwolisz.

Jeśli chodzi o to, które przeglądarki obsługują CORS, Wikipedia twierdzi, że obsługują go następujące silniki:

  • Gecko 1.9.1 (FireFox 3.5)
  • WebKit (Safari 4, Chrome 3)
  • MSHTML / Trident 6 (IE10) z częściowym wsparciem w IE8 i 9
  • Presto (Opera 12)

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

Mike Goodwin
źródło
Cześć Mike. Dziękuję za link, oto kolejny, który też jest dobry: codeguru.com/csharp/.net/net_asp/ ... - chociaż żadne z nich nie rozwiązało jeszcze problemu. Na razie umieściłem moje strony testowe na serwerze i to pomaga mi na krótką metę. Próbowałem zainstalować Microsoft.AspNet.WebApi.Cors, ale pojawił się dziwny błąd, że moja aplikacja nie ma żadnych zależności WebApi, więc instalacja została wycofana. Dziękuję za odpowiedź - wiem, że to prawda. +1!
rwkiii
@rwkiii Link jest rzeczywiście rozwiązaniem, które wymaga dodania zależności od Web API 5.2.2, ale rozwiązanie byłoby bardziej rozszerzalne niż hack, aby zmusić MVC do obsługi żądania OPCJI przed lotem. Możesz również zechcieć przejrzeć odpowiedź Dominicka, ponieważ prośba przed lotem może być wynikiem Akceptuj LUB nagłówków Content-Type, które wymagają takiego połączenia od klienta
Sudhanshu Mishra,
Uwaga, ale jeśli ustawisz typ zawartości na: „application / x-www-form-urlencoded”, „multipart / form-data” lub „text / plain”, wówczas żądanie zostanie uznane za „proste” i nie spowoduje wydania wniosek przed lotem.
mbx-mbx
94

Odpowiedź Mike'a Goodwina jest świetna, ale kiedy próbowałem, wydawało się, że jest skierowana do MVC5 / WebApi 2.1. Zależności dla Microsoft.AspNet.WebApi.Cors nie grały dobrze z moim projektem MVC4.

Najprostszy sposób włączenia CORS w WebApi z MVC4 był następujący.

Zauważ, że zezwoliłem na wszystko, sugeruję, abyś ograniczył Origin tylko do klientów, których chcesz, aby Twoje API obsługiwało. Zezwolenie na wszystko jest zagrożeniem dla bezpieczeństwa.

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:

Robimy to, aby zezwolić na czasownik http OPTIONS

 public class BaseApiController : ApiController
  {
    public HttpResponseMessage Options()
    {
      return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
    }
  }
Oliver
źródło
@Castaldi Dzieje się tak prawdopodobnie dlatego, że podana odpowiedź dotyczyła WebApi 1, który nie miał routingu atrybutów. W przypadku WebApi 2 sugerowałbym użycie pakietu CORS nuget firmy Microsoft. nuget.org/packages/Microsoft.AspNet.WebApi.Cors
Oliver
Możesz również użyć [ApiExplorerSettings (IgnoreApi = true)], aby zignorować punkty końcowe OPTIONS w Swagger.
Mário Meyrelles
To zadziałało dla mojej aplikacji WebApi 2. Szczególnie metoda Options () do odpowiedniego / podstawowego kontrolera
lazyList
24

Po prostu dodaj to do swojej Application_OnBeginRequestmetody (umożliwi to globalną obsługę CORS dla Twojej aplikacji) i „obsłuż” żądania inspekcji wstępnej:

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

* bezpieczeństwo: pamiętaj, że umożliwi to wysyłanie żądań Ajax z dowolnego miejsca na Twój serwer (zamiast tego możesz zezwolić na tworzenie listy źródeł / adresów URL oddzielonych przecinkami, jeśli wolisz).

Użyłem aktualnego pochodzenia klienta zamiast, *ponieważ pozwoli to na ustawienie poświadczeń =>Access-Control-Allow-Credentials na true umożliwi zarządzanie sesjami w różnych przeglądarkach

musisz również włączyć czasowniki usuwania i wstawiania, poprawiania i opcji w swojej webconfigsekcji system.webServer, w przeciwnym razie IIS je zablokuje:

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

mam nadzieję że to pomoże

Chtiwi Malek
źródło
3
Dzięki. Tylko to Application_OnBeginRequestmi pomogło. Ale jeśli chcesz mieć również możliwość uzyskania danych z autoryzacją, powinieneś również dodać AuthorizationdoAccess-Control-Allow-Headers
Ehsan88
18

Po napotkaniu tego samego problemu w projekcie Web API 2 (i niemożności korzystania ze standardowych pakietów CORS z powodów, których nie warto tutaj omawiać), udało mi się rozwiązać ten problem, wdrażając niestandardowy 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;
    }
}

W przypadku konfiguracji interfejsu API sieci Web:

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

Zauważ, że mam również włączone nagłówki CORS w Web.config, podobnie jak niektóre inne odpowiedzi zamieszczone tutaj:

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

Zauważ, że mój projekt nie zawiera MVC, tylko Web API 2.

definiuje
źródło
10

Udało mi się przezwyciężyć błędy 405 i 404 wyrzucane w żądaniach opcji ajax przed lotem tylko przez niestandardowy kod w 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: Weź pod uwagę kwestie bezpieczeństwa, zezwalając na wszystko *.

Musiałem wyłączyć CORS, ponieważ zwracał nagłówek „Access-Control-Allow-Origin” zawierający wiele wartości.

Potrzebne także w 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>

Aplikacja app.pool musi być ustawiona na tryb zintegrowany.

lukalev
źródło
8

Miałem ten sam problem. Dla mnie rozwiązaniem było usunięcie niestandardowego typu zawartości z wywołania jQuery AJAX. Niestandardowe typy treści wyzwalają żądanie przed wyświetleniem. Znalazłem to:

Przeglądarka może pominąć żądanie inspekcji wstępnej, jeśli są spełnione następujące warunki:

Metoda żądania jest GET, HEADlub POST, i

Aplikacja nie ustawia żadnych innych nagłówków żądań niż Accept , Accept-Language, Content-Language, Content-Type, lub Last-Event-ID, a

Content-TypeNagłówka (jeśli jest ustawione) to jedna z poniższych sytuacji:

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

Z tej strony: http://www.asp.net/web-api/overview/security/enables-cross-origin-requests-in-web-api (w sekcji „Żądania inspekcji wstępnej”)

Dominik
źródło
2
    protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 405 && Context.Request.HttpMethod == "OPTIONS" )
        {
            Response.Clear();
            Response.StatusCode = 200;
            Response.End();
        }
    }
yongfa365
źródło
1

Ja też stanąłem przed tym samym problemem.

Wykonaj poniższy krok, aby rozwiązać problem dotyczący zgodności (CORS) w przeglądarkach.

Uwzględnij REDRock w swoim rozwiązaniu z numerem referencyjnym Cors. Dołącz odwołanie do WebActivatorEx do rozwiązania internetowego interfejsu API.

Następnie dodaj plik CorsConfig do folderu App_Start interfejsu 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());
        }
    }
}

Po wprowadzeniu tych zmian mogłem uzyskać dostęp do interfejsu webapi we wszystkich przeglądarkach.

Praveen Thangaraja
źródło
4
Co to jest Redrock? Wyszukałem w Google i wyszukałem pakiet Nuget i nic nie wróciło. Link byłby dobry.
hofnarwillie
1

Miałem ten sam problem i tak to naprawiłem:

Po prostu wrzuć to do swojego 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>
Erti-Chris Eelmaa
źródło
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>
Roy Fagon
źródło