Cors-preflightverzoeken voor ASP.NET MVC-acties afhandelen

Ik probeer een POST-verzoek voor meerdere domeinen uit te voeren naar een ASP.NET MVC-controlleractie. Deze controlleractie accepteert & maakt gebruik van verschillende parameters. Het probleem is dat wanneer het preflight-verzoek plaatsvindt, de controlleractie daadwerkelijk probeert om & omdat het OPTIONS-verzoek geen gegevens doorgeeft, genereert de controlleractie een 500 HTTP-fout. Als ik de code verwijder die de parameter gebruikt, of de parameter zelf, wordt de hele aanvraagketen met succes voltooid.

Een voorbeeld van hoe dit wordt geïmplementeerd:

Actie van controller

public ActionResult GetData(string data)
{
    return new JsonResult
    {
        Data = data.ToUpper(),
        JsonRequestBehavior = JsonRequestBehavior.AllowGet
    };
}

Client-side code

<script type="text/javascript">
        $(function () {
            $("#button-request").click(function () {
                var ajaxConfig = {
                    dataType: "json",
                    url: "http://localhost:8100/host/getdata",
                    contentType: 'application/json',
                    data: JSON.stringify({ data: "A string of data" }),
                    type: "POST",
                    success: function (result) {
                        alert(result);
                    },
                    error: function (jqXHR, textStatus, errorThrown) {
                        alert('Error: Status: ' + textStatus + ', Message: ' + errorThrown);
                    }
                };
                $.ajax(ajaxConfig);
            });
        });
    </script>

Als het preflight-verzoek nu plaatsvindt, wordt een 500 HTTP-code geretourneerd, omdat de parameter “data” null is, aangezien het OPTIONS-verzoek geen waarden doorgeeft.

De servertoepassing is ingesteld in mijn lokale IIS op poort 8100 & de pagina waarop de code aan de clientzijde wordt uitgevoerd, is ingesteld op poort 8200 om de interdomein-aanroepen na te bootsen.

Ik heb de host (op 8100) ook geconfigureerd met de volgende headers:

Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Origin: http://localhost:8200

Een oplossing die ik had gevonden, was het controleren van de HTTP-methode die de actie uitvoert & als het een OPTIONS-verzoek is om alleen lege inhoud te retourneren, voer anders de actiecode uit. Vind ik leuk:

public ActionResult GetData(string data)
{
    if (Request.HttpMethod == "OPTIONS") {
        return new ContentResult();
    } else {
        return new JsonResult
        {
            Data = data.ToUpper(),
            JsonRequestBehavior = JsonRequestBehavior.AllowGet
        };
    }
}

Maar deze aanpak vind ik erg onhandig. Ik heb overwogen om dit soort logica toe te voegen aan een Attribute, maar zelfs dit zou betekenen dat elke actie die wordt aangeroepen wordt versierd met CORS.

Is er een elegantere oplossing om deze functionaliteit te laten werken?


Antwoord 1, autoriteit 100%

Dus ik heb een oplossing gevonden die werkt. Voor elk verzoek controleer ik of het een CORS-verzoek is & of het verzoek binnenkomt met het werkwoord OPTIES, wat aangeeft dat het het preflight-verzoek is. Als dat zo is, stuur ik gewoon een lege reactie terug (die natuurlijk alleen de headers bevat die in IIS zijn geconfigureerd), waardoor de uitvoering van de controlleractie wordt genegeerd.

Als de client vervolgens bevestigt dat het is toegestaan ​​om het verzoek uit te voeren op basis van de geretourneerde headers van preflight, wordt de daadwerkelijke POST uitgevoerd & de controlleractie wordt uitgevoerd. En voorbeeld van mijn code:

protected void Application_BeginRequest()
{
    if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
        Request.HttpMethod == "OPTIONS") {
        Response.Flush();
    }
}

Zoals gezegd werkte dit voor mij, maar als iemand een betere manier weet, of fouten in mijn huidige implementatie, hoor ik het graag.


Antwoord 2, autoriteit 18%

Ik ga verder op het antwoord van Carl, ik nam zijn code en stopte die in mijn OWIN-pijplijn:

app.Use((context, next) =>
{
     if (context.Request.Headers.Any(k => k.Key.Contains("Origin")) && context.Request.Method == "OPTIONS")
     {
         context.Response.StatusCode = 200;
         return context.Response.WriteAsync("handled");
     }
     return next.Invoke();
});

Voeg dit gewoon toe aan het begin (of waar dan ook voordat u de WebAPI registreert) van uw IAppBuilder in Startup.cs


Antwoord 3, autoriteit 10%

Het geaccepteerde antwoord werkt als een tierelier, maar ik ontdekte dat het verzoek daadwerkelijk werd doorgegeven aan de controller. Ik ontving een 200statuscode, maar de antwoordtekst bevatte veel HTML met uitzondering van de controller. Dus in plaats van Response.Flush()te gebruiken, vond ik het beter om Response.End()te gebruiken, wat de uitvoering van het verzoek wel stopt. Deze alternatieve oplossing ziet er als volgt uit:

EDIT:een typefout hersteld van het oorspronkelijke antwoord.

protected void Application_BeginRequest()
{
    if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
        Request.HttpMethod == "OPTIONS") {
        Response.End();
    }
}

Antwoord 4, autoriteit 9%

Hier is hoe ik de preflight/CORS-problemen met ASP.Net Web Api heb opgelost. Ik heb eenvoudig het Microsoft.AspNet.WebApi.Cors Nuget-pakket toegevoegd aan mijn webproject. Vervolgens heb ik in mijn WebApiConfig.cs-bestand deze regel toegevoegd:

config.EnableCors(new ApplicationCorsPolicy());

en een aangepaste PolicyProvider-klasse gemaakt

public class ApplicationCorsPolicy : Attribute, ICorsPolicyProvider
{
    public async Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var corsRequestContext = request.GetCorsRequestContext();
        var originRequested = corsRequestContext.Origin;
        if (await IsOriginFromAPaidCustomer(originRequested))
        {
            // Grant CORS request
            var policy = new CorsPolicy
            {
                AllowAnyHeader = true,
                AllowAnyMethod = true
            };
            policy.Origins.Add(originRequested);
            return policy;
        }
        // Reject CORS request
        return null;
    }
    private async Task<bool> IsOriginFromAPaidCustomer(string originRequested)
    {
        // Do database look up here to determine if origin should be allowed.
        // In my application I have a table that has a list of domains that are
        // allowed to make API requests to my service. This is validated here.
        return true;
    }
}

Kijk, met het Cors-framework kun je je eigen logica toevoegen om te bepalen welke oorsprongen zijn toegestaan, enz. Dit is erg handig als je een REST API aan de buitenwereld blootstelt en de lijst met mensen (oorsprongen) die toegang hebben uw site bevindt zich in een gecontroleerde omgeving zoals een database. Als je nu gewoon alle oorsprong toestaat (wat misschien niet in alle gevallen zo’n goed idee is), kun je dit gewoon doen in WebApiConfig.cs om CORS wereldwijd in te schakelen:

config.EnableCors();

Net als filters en handlers in WebApi kunt u als volgt annotaties op klasse- of methodeniveau aan uw controllers toevoegen:

[EnableCors("*, *, *, *")]

Merk op dat het kenmerk EnableCors een constructor heeft die de volgende parameters accepteert

  1. Lijst van toegestane herkomsten
  2. Lijst met toegestane verzoekheaders
  3. Lijst met toegestane HTTP-methoden
  4. Lijst met toegestane antwoordheaders

Je kunt op elke controller/eindpunt statisch specificeren wie toegang heeft tot welke bron.

Update 24/06/2016:
Ik moet vermelden dat ik het volgende in mijn Web.config heb. Het lijkt erop dat dit niet voor iedereen de standaardinstellingen zijn.

<system.webServer>
    <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>
</system.webServer>

Bron: Microsoft


Antwoord 5, autoriteit 4%

Geen van deze antwoorden werkte voor mij, maar de volgende webconfig-instellingen wel. De twee belangrijkste instellingen voor mij waren het instellen van Access-Control-Allow-Headersop Content-Typeen commentaar geven op de regel die de OPTIONSVerbHandlerverwijdert:

 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"></modules>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type" />
      </customHeaders>
    </httpProtocol>
    <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>
  </system.webServer>

Antwoord 6, autoriteit 3%

Dit kan een rode haring zijn. Ik heb onlangs CORS goed laten werken zonder door een van de hoepels te springen die je aan het doen bent.

Dit werd gedaan met behulp van een combinatie van Thinktecture.IdentityModel nuget-pakket, en nog belangrijker… VERWIJDERING van alle verwijzingen naar WebDAV. Dit omvat het verwijderen van de webdav-module uit IIS en het zorgen voor de volgende regels in uw webconfiguratie:

<system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="WebDAVModule" />
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
    </modules>
    <handlers>
      <remove name="WebDAV" />
      <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>

Dan kun je thinktecture gebruiken om je CORS vanuit je Global.asax te configureren met een statische klasse zoals deze:

public class CorsConfig
{
    public static void RegisterCors(HttpConfiguration httpConfiguration)
    {
        var corsConfig = new WebApiCorsConfiguration();
        corsConfig.RegisterGlobal(httpConfiguration);
        corsConfig.ForAllResources().AllowAllOriginsAllMethodsAndAllRequestHeaders();
    }
}

BRON: http://brockallen.com/2012/06/28/cors-support-in-webapi-mvc-and-iis-with-thinktecture-identitymodel/

Other episodes