Unterstützung des Verbs HTTP OPTIONS in der ASP.NET MVC / WebAPI-Anwendung

78

Ich habe eine ASP.NET-Webanwendung eingerichtet, die mit einer MVC 4 / Web-API-Vorlage beginnt. Es scheint, als ob die Dinge wirklich gut funktionieren - keine Probleme, die mir bewusst sind. Ich habe Chrome und Firefox verwendet, um die Website zu durchsuchen. Ich habe mit Fiddler getestet und alle Antworten scheinen auf dem Geld zu sein.

Jetzt schreibe ich eine einfache Test.aspx, um diese neue Web-API zu verwenden. Die relevanten Teile des Skripts:

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

Dies erzeugt einen REQUEST-Header:

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

Die Web-API gibt eine 405-Methode zurück, die nicht zulässig ist.

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>

Ich verstehe, dass das Verb OPTIONS standardmäßig nicht in Web-API-Controllern verkabelt ist ... Also habe ich den folgenden Code in meine UserController.cs eingefügt:

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

... und dies beseitigte den Fehler 405 Method Not Allowed, aber die Antwort ist vollständig leer - es werden keine Daten zurückgegeben:

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

Es muss zusätzliche Logik geben ... Ich weiß nicht, wie man die Options-Methode richtig codiert oder ob der Controller überhaupt der richtige Ort ist, um den Code einzufügen. Seltsam (für mich), dass die Web-API-Site bei Betrachtung von Firefox oder Chrome richtig reagiert, der obige .ajax-Aufruf jedoch Fehler ausgibt. Wie gehe ich mit der "Preflight" -Prüfung im .ajax-Code um? Vielleicht sollte ich dieses Problem in der .ajax-Logik der Client-Seite ansprechen? Oder wenn dies ein Problem auf der Serverseite ist, weil das Verb OPTIONS nicht behandelt wird.

Kann jemand helfen? Dies muss ein sehr häufiges Problem sein und ich entschuldige mich, wenn es hier beantwortet wurde. Ich habe gesucht, aber keine Antworten gefunden, die geholfen haben.

UPDATE IMHO, dies ist ein clientseitiges Problem und hat mit dem obigen Ajax JQuery-Code zu tun. Ich sage dies, weil Fiddler keine 405-Fehlerheader anzeigt, wenn ich über einen Webbrowser auf mywebapidomain / api / user zugreife. Der einzige Ort, an dem ich dieses Problem duplizieren kann, ist der Aufruf von JQuery .ajax (). Der oben genannte identische Ajax-Aufruf funktioniert auch einwandfrei, wenn er auf dem Server (derselben Domäne) ausgeführt wird.

Ich habe einen anderen Beitrag gefunden: Prototyp-AJAX-Anfrage wird als OPTIONS anstatt als GET gesendet; führt zu 501 Fehlern , die verwandt zu sein scheinen, aber ich habe an ihren Vorschlägen ohne Erfolg herumgebastelt. Anscheinend ist JQuery so codiert, dass, wenn eine Ajax-Anfrage domänenübergreifend ist (was meine ist), ein paar Header hinzugefügt werden, die den OPTIONS-Header irgendwie auslösen.

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

Es scheint nur, dass es eine bessere Lösung geben sollte, als den Kerncode in JQuery zu ändern ...

Bei der folgenden Antwort wird davon ausgegangen, dass es sich um ein serverseitiges Problem handelt. Vielleicht, denke ich, aber ich neige mich zum Kunden und es wird nicht helfen, einen Hosting-Anbieter anzurufen.

rwkiii
quelle
Was werden Sie mit Ihrer Optionsanfrage senden?
Daniel A. White
Ich muss überhaupt keine OPTIONS-Anfrage senden. Aus irgendeinem Grund geschieht dies, wenn ein Ajax-Aufruf domänenübergreifend erfolgt. Wie Sie im Javascript sehen können, spezifiziere ich nur GET, aber der OPTIONS-Header wird aufgrund des HTTP-Protokolls gesendet. Es ist ein "Preflight" Check.
rwkiii
2
oh du solltest cors auf deinem iis server aktivieren.
Daniel A. White
Es ist ein Arvixe-Server - Business Class Pro. Beide Sites werden auf demselben physischen Server und demselben Hosting-Konto gehostet. Nur verschiedene Hostnamen. Kann ich CORS aktivieren, ohne Arvixe anzurufen?
rwkiii
Ich würde Ihren Hosting-Anbieter anrufen.
Daniel A. White

Antworten:

52

Wie Daniel A. White in seinem Kommentar sagte, wird die OPTIONS-Anforderung höchstwahrscheinlich vom Client als Teil einer domänenübergreifenden JavaScript-Anforderung erstellt. Dies erfolgt automatisch durch CORS-kompatible Browser (Cross Origin Resource Sharing). Die Anfrage ist eine vorläufige Anfrage oder eine Anfrage vor dem Flug , die vor der eigentlichen AJAX-Anfrage gestellt wird, um zu bestimmen, welche Anforderungsverben und -header für CORS unterstützt werden. Der Server kann wählen, ob er keine, alle oder einige der HTTP-Verben unterstützt.

Um das Bild zu vervollständigen, verfügt die AJAX-Anforderung über einen zusätzlichen "Origin" -Header, der angibt, woher die ursprüngliche Seite stammt, auf der sich das JavaScript befindet. Der Server kann wählen, ob Anforderungen von einem beliebigen Ursprung oder nur für eine Reihe bekannter, vertrauenswürdiger Ursprünge unterstützt werden sollen. Das Zulassen eines Ursprungs ist ein Sicherheitsrisiko, da dies das Risiko einer Cross Site Request Forgery (CSRF) erhöhen kann.

Sie müssen also CORS aktivieren.

Hier ist ein Link, der erklärt, wie dies in der ASP.Net-Web-API gemacht wird

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

Mit der dort beschriebenen Implementierung können Sie unter anderem angeben

  • CORS-Unterstützung pro Aktion, pro Controller oder global
  • Die unterstützten Ursprünge
  • Beim Aktivieren von CORS aa Controller oder globaler Ebene werden die unterstützten HTTP-Verben verwendet
  • Gibt an, ob der Server das Senden von Anmeldeinformationen mit Ursprungsanforderungen unterstützt

Im Allgemeinen funktioniert dies einwandfrei, Sie müssen jedoch sicherstellen, dass Sie sich der Sicherheitsrisiken bewusst sind, insbesondere wenn Sie Cross-Origin-Anforderungen von einer beliebigen Domain zulassen. Überlegen Sie genau, bevor Sie dies zulassen.

In Bezug darauf, welche Browser CORS unterstützen, sagt Wikipedia, dass die folgenden Engines dies unterstützen:

  • Gecko 1.9.1 (FireFox 3.5)
  • WebKit (Safari 4, Chrome 3)
  • MSHTML / Trident 6 (IE10) mit teilweiser Unterstützung in IE8 und 9
  • Presto (Oper 12)

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

Mike Goodwin
quelle
Hallo Mike. Vielen Dank für den Link, hier ist noch einer, der auch gut ist: codeguru.com/csharp/.net/net_asp/… - obwohl keiner von beiden das Problem für mich bisher behoben hat. Ich habe meine Testseiten vorerst auf dem Server platziert und das hilft mir kurzfristig. Ich habe versucht, Microsoft.AspNet.WebApi.Cors zu installieren, habe jedoch einen seltsamen Fehler erhalten, dass meine App keine WebApi-Abhängigkeiten hatte und die Installation daher rückgängig gemacht wurde. Vielen Dank für Ihre Antwort - ich weiß, dass es richtig ist. +1!
rwkiii
@rwkiii Der Link ist in der Tat eine Lösung, bei der Abhängigkeiten von der Web-API 5.2.2 hinzugefügt werden. Die Lösung wäre jedoch erweiterbarer als ein Hack, um MVC zu zwingen, die OPTIONS-Anforderung vor dem Flug zu unterstützen. Möglicherweise möchten Sie auch die Antwort von Dominick überprüfen, da die Anfrage vor dem Flug möglicherweise auf Header vom Typ Akzeptieren ODER Inhaltstyp zurückzuführen ist, für die ein solcher Anruf vom Kunden erforderlich ist
Sudhanshu Mishra,
Nur eine Anmerkung, aber wenn Sie den Inhaltstyp auf "application / x-www-form-urlencoded", "multipart / form-data" oder "text / plain" setzen, wird die Anfrage als "einfach" betrachtet und gibt die nicht aus Anfrage vor dem Flug.
Magrangs
93

Die Antwort von Mike Goodwin ist großartig, aber als ich sie ausprobierte, schien sie auf MVC5 / WebApi 2.1 ausgerichtet zu sein. Die Abhängigkeiten für Microsoft.AspNet.WebApi.Cors haben mit meinem MVC4-Projekt nicht gut funktioniert.

Der einfachste Weg, CORS auf WebApi mit MVC4 zu aktivieren, war der folgende.

Beachten Sie, dass ich alle zugelassen habe. Ich schlage vor, dass Sie die Origin nur auf die Clients beschränken, für die Ihre API bereitgestellt werden soll. Alles zuzulassen ist ein Sicherheitsrisiko.

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:

Wir tun dies, um das http-Verb OPTIONS zuzulassen

 public class BaseApiController : ApiController
  {
    public HttpResponseMessage Options()
    {
      return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
    }
  }
Oliver
quelle
@ Castaldi Dies ist wahrscheinlich, weil die Antwort auf WebApi 1 gerichtet war, das kein Attribut-Routing hatte. Für WebApi 2 würde ich die Verwendung des CORS-Nuget-Pakets von Microsoft vorschlagen. nuget.org/packages/Microsoft.AspNet.WebApi.Cors
Oliver
Sie können auch [ApiExplorerSettings (IgnoreApi = true)] verwenden, um die OPTIONS-Endpunkte auf Swagger zu ignorieren.
Mário Meyrelles
Dies funktionierte für meine WebApi 2 App. Insbesondere die Options () -Methode zum relevanten / Basis-Controller
lazyList
24

Fügen Sie dies einfach Ihrer Application_OnBeginRequestMethode hinzu (dies aktiviert die CORS-Unterstützung global für Ihre Anwendung) und "bearbeiten" Sie Preflight-Anforderungen:

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

* Sicherheit: Beachten Sie, dass dies Ajax-Anforderungen von überall an Ihren Server ermöglicht (Sie können stattdessen nur eine durch Kommas getrennte Liste von Ursprüngen / URLs zulassen, wenn Sie dies bevorzugen).

Ich habe stattdessen den aktuellen Client-Ursprung verwendet, *da dies Anmeldeinformationen ermöglicht => Die Einstellung Access-Control-Allow-Credentialsauf true ermöglicht die browserübergreifende Sitzungsverwaltung

Außerdem müssen Sie die Verben zum Löschen und Einfügen, Patchen und Optionen in Ihrem webconfigAbschnitt aktivieren system.webServer, andernfalls blockiert IIS sie:

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

hoffe das hilft

Chtiwi Malek
quelle
3
Vielen Dank. Nur das Application_OnBeginRequesthat mir geholfen. Aber wenn Sie wollen in der Lage sein , Daten zu mit Genehmigung zu erhalten, sollten Sie auch hinzufügen AuthorizationzuAccess-Control-Allow-Headers
Ehsan88
18

Nachdem ich in einem Web-API-2-Projekt auf dasselbe Problem gestoßen war (und die Standard-CORS-Pakete aus Gründen, die hier nicht behandelt werden sollten, nicht verwenden konnte), konnte ich dieses Problem beheben, indem ich einen benutzerdefinierten DelagatingHandler implementierte:

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

Für die Web-API-Konfiguration:

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

Beachten Sie, dass ich auch die CORS-Header in Web.config aktiviert habe, ähnlich wie bei einigen anderen hier veröffentlichten Antworten:

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

Beachten Sie, dass mein Projekt keine MVC enthält, sondern nur die Web-API 2.

definiert
quelle
10

Ich habe es geschafft, 405- und 404-Fehler zu überwinden, die bei Ajax-Optionsanfragen vor dem Flug nur durch benutzerdefinierten Code in global.asax ausgelöst wurden

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: Berücksichtigen Sie Sicherheitsprobleme, wenn Sie alles zulassen *.

Ich musste CORS deaktivieren, da der Header 'Access-Control-Allow-Origin' mehrere Werte enthält.

Auch benötigt dies in 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>

Und app.pool muss auf den integrierten Modus eingestellt sein.

lukalev
quelle
7

Ich hatte das gleiche Problem. Für mich bestand die Lösung darin, den benutzerdefinierten Inhaltstyp aus dem jQuery AJAX-Aufruf zu entfernen. Benutzerdefinierte Inhaltstypen lösen die Anforderung vor dem Flug aus. Ich habe das gefunden:

Der Browser kann die Preflight-Anforderung überspringen, wenn die folgenden Bedingungen erfüllt sind:

Das Anforderungsverfahren ist GET, HEADoder POST, und

Die Anwendung stellt keine Request - Header außer Accept, Accept-Language, Content-Language, Content-Type, oder Last-Event-ID, und

Der Content-TypeHeader (falls gesetzt) ​​ist einer der folgenden:

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

Von dieser Seite: http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api (unter "Preflight-Anfragen")

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

Auch ich stand vor dem gleichen Problem.

Führen Sie die folgenden Schritte aus, um das Problem mit der CORS-Konformität in Browsern zu beheben.

Nehmen Sie REDRock mit der Cors-Referenz in Ihre Lösung auf. Fügen Sie einen WebActivatorEx-Verweis auf die Web-API-Lösung hinzu.

Fügen Sie dann die Datei CorsConfig im Ordner Web API App_Start hinzu.

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

Mit diesen Änderungen konnte ich in allen Browsern auf das Webapi zugreifen.

Praveen Thangaraja
quelle
4
Was ist Redrock? Ich habe eine Google-Suche und eine Nuget-Paketsuche durchgeführt und nichts zurückgegeben. Ein Link wäre gut.
Hofnarwillie
1

Ich hatte das gleiche Problem und habe es so behoben:

Wirf dies einfach in deine 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
quelle
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
quelle