Manipulando solicitações de CORS Preflight para ações da ASP.NET MVC

Estou tentando executar uma solicitação POST entre domínios para uma ação do controlador ASP.NET MVC. Esta ação do controlador aceita e usa vários parâmetros. O problema é que, quando a solicitação de preflight acontece, a ação do controlador realmente tenta executar e porque a solicitação OPTIONS não passa nenhum dado, a ação do controlador lança um erro HTTP 500. Se eu remover o código que usa o parâmetro ou o próprio parâmetro, toda a cadeia de solicitações será concluída com êxito.

Um exemplo de como isso é implementado:

Ação do Controlador

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

Código do lado do cliente

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

Agora, sempre que a solicitação de comprovação acontece, ela retorna um código HTTP 500, porque o parâmetro “data” é nulo, visto que a solicitação OPTIONS não passa nenhum valor.

O aplicativo do servidor foi configurado no IIS local na porta 8100 e a página que executa o código do lado do cliente está configurada na porta 8200 para imitar as chamadas de domínio cruzado.

Eu também configurei o host (em 8100) com os seguintes headers:

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

Uma solução que eu encontrei, foi verificar o método HTTP que executa a ação e se é uma solicitação OPTIONS para retornar apenas o conteúdo em branco, caso contrário, execute o código de ação. Igual a:

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

Mas essa abordagem parece muito desajeitada para mim. Considerei adicionar esse tipo de lógica a um Attribute , mas mesmo isso significaria decorar todas as ações que serão chamadas usando o CORS com ele.

Existe uma solução mais elegante para que essa funcionalidade funcione?

Então eu encontrei uma solução que funciona. Para cada solicitação, verifico se é uma solicitação de CORS e se a solicitação está chegando com o verbo OPTIONS, indicando que é a solicitação de comprovação. Se estiver, apenas envio uma resposta vazia de volta (que contém apenas os headers configurados no IIS, é claro), negando assim a execução da ação do controlador.

Então, se o cliente confirmar que é permitido executar a solicitação com base nos headers retornados da pré-impressão, o POST real será executado e a ação do controlador será executada. E exemplo do meu código:

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

Como mencionado, isso funcionou para mim, mas se alguém souber de uma maneira melhor, ou de quaisquer falhas na minha implementação atual, eu apreciaria ouvir sobre eles.

Expandindo a resposta de Carl, peguei seu código e o conectei ao meu pipeline OWIN:

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

Basta adicionar isso ao início (ou em qualquer lugar antes de registrar o WebAPI) do seu IAppBuilder no Startup.cs

A resposta aceita funciona como um encanto, mas descobri que a solicitação estava sendo passada para o controlador. Eu estava recebendo um código de status 200 , mas o corpo de resposta continha muito HTML com uma exceção do controlador. Então, em vez de usar Response.Flush() , descobri que era melhor usar Response.End() , o que impede a execução da solicitação. Essa solução alternativa ficaria assim:

EDIT: corrigido um erro de digitação da resposta original.

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

Nenhuma dessas respostas funcionou para mim, mas as seguintes configurações do webconfig funcionaram. As duas configurações principais para mim foram configurar o Access-Control-Allow-Headers para Content-Type e comentar a linha que remove o OPTIONSVerbHandler :

                 

Aqui está como eu lidei com os problemas de preflight / CORS com o ASP.Net Web Api. Eu simplesmente adicionei o pacote Microsoft.AspNet.WebApi.Cors Nuget ao meu projeto da Web.Em seguida, no meu arquivo WebApiConfig.cs, adicionei esta linha:

 config.EnableCors(new ApplicationCorsPolicy()); 

e criou uma class PolicyProvider personalizada

 public class ApplicationCorsPolicy : Attribute, ICorsPolicyProvider { public async Task 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 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; } } 

Veja, o framework Cors permite que você adicione sua própria lógica para determinar quais origens são permitidas, etc. Isso é muito útil se você está expondo uma API REST para o mundo externo e a lista de pessoas (origens) que podem acessar seu site é em um ambiente controlado como um database. Agora, se você estiver simplesmente permitindo todas as origens (o que pode não ser uma boa ideia em todos os casos), basta fazer isso em WebApiConfig.cs para ativar o CORS globalmente:

 config.EnableCors(); 

Assim como os Filtros e Manipuladores no WebApi, você também pode adicionar annotations de nível de class ou método aos seus controladores da seguinte forma:

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

Observe que o atributo EnableCors tem um construtor que aceita os seguintes parâmetros

  1. Lista de Origens Permitidas
  2. Lista de headers de solicitação permitidos
  3. Lista de methods HTTP permitidos
  4. Lista de headers de resposta permitidos

Você pode especificar estaticamente em cada controlador / terminal que tem permissão para acessar qual recurso.

Atualização 24/06/2016: devo mencionar que tenho o seguinte no meu Web.config. Parece que estes podem não ser os padrões para todos.

         

Fonte: Microsoft

Isso pode ser um arenque vermelho. Eu tenho recentemente o CORS funcionando bem sem saltar através de qualquer um dos aros que você está fazendo.

Isso foi feito usando uma combinação do pacote nuget Thinktecture.IdentityModel e, o mais importante … REMOÇÃO de todas as referências ao WebDAV. Isso inclui remover o módulo webdav do IIS e garantir as seguintes linhas em sua configuração da web:

                  

Então você pode apenas usar o thinktecture para configurar seu CORS a partir do seu Global.asax usando uma class estática como esta:

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

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