Обработка запросов CORS для предпросмотра действий ASP.NET MVC
Я пытаюсь выполнить кросс-доменный запрос POST для действия контроллера ASP.NET MVC. Это действие контроллера принимает и использует различные параметры. Проблема заключается в том, что при выполнении запроса предварительной проверки действие контроллера фактически пытается выполнить, и поскольку запрос OPTIONS не передает никаких данных, действие контроллера выдает 500 HTTP-ошибок. Если я удалю код, который использует этот параметр, или сам параметр, вся цепочка запросов будет успешно завершена.
Пример того, как это реализовано:
Действие контроллера
public ActionResult GetData(string data)
{
return new JsonResult
{
Data = data.ToUpper(),
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
Клиентский код
<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>
Теперь, всякий раз, когда выполняется запрос предварительной проверки, он возвращает 500 HTTP-код, потому что параметр "data" равен NULL, поскольку запрос OPTIONS не передает никаких значений.
Серверное приложение было настроено в моем локальном IIS на порту 8100, а страница, на которой выполняется клиентский код, настроена на порт 8200, чтобы имитировать междоменные вызовы.
Я также настроил хост (на 8100) со следующими заголовками:
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Origin: http://localhost:8200
Обходной путь, который я нашел, состоял в том, чтобы проверить HTTP-метод, который выполняет действие, и если это запрос OPTIONS, чтобы просто вернуть пустой контент, в противном случае выполните код действия. Например:
public ActionResult GetData(string data)
{
if (Request.HttpMethod == "OPTIONS") {
return new ContentResult();
} else {
return new JsonResult
{
Data = data.ToUpper(),
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
Но этот подход кажется мне очень неуклюжим. Я подумал о добавлении этой логики к Attribute
, но даже это означало бы украшение каждого действия, которое будет вызвано с помощью CORS с ним.
Есть ли более элегантное решение для работы этой функции?
Ответы
Ответ 1
Итак, я нашел решение, которое работает. Для каждого запроса я проверяю, является ли это запросом CORS и поступает ли запрос с глаголом OPTIONS, указывая, что это предварительный запрос. Если это так, я просто отправляю пустой ответ (который, конечно, содержит только заголовки, настроенные в IIS), тем самым сводя на нет выполнение действия контроллера.
Затем, если клиент подтверждает, что ему разрешено выполнить запрос на основе возвращенных заголовков из предварительной проверки, выполняется фактическая проверка POST и выполняется действие контроллера. И пример моего кода:
protected void Application_BeginRequest()
{
if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
Request.HttpMethod == "OPTIONS") {
Response.Flush();
}
}
Как уже упоминалось, это работало для меня, но если кто-то знает о лучшем способе или о каких-либо недостатках в моей текущей реализации, я был бы рад услышать о них.
Ответ 2
расширяя ответ на Carl, я взял его код и подключил его к моему конвейеру 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();
});
Просто добавьте это в начало (или где-нибудь до того, как вы зарегистрируете WebAPI) своего IAppBuilder в Startup.cs
Ответ 3
Принятый ответ работает как шарм, но я обнаружил, что запрос фактически передавался контроллеру. Я получал код состояния 200
, но тело ответа содержало много HTML с исключением из контроллера. Поэтому вместо использования Response.Flush()
я обнаружил, что лучше использовать Response.End()
, что останавливает выполнение запроса. Это альтернативное решение будет выглядеть так:
РЕДАКТИРОВАТЬ: зафиксирована опечатка, выполненная из исходного ответа.
protected void Application_BeginRequest()
{
if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
Request.HttpMethod == "OPTIONS") {
Response.End();
}
}
Ответ 4
Вот как я обработал проблемы перед полетом /CORS с помощью ASP.Net Web Api. Я просто добавил пакет Microsoft.AspNet.WebApi.Cors Nuget в свой веб-проект. Затем в моем файле WebApiConfig.cs я добавил эту строку:
config.EnableCors(new ApplicationCorsPolicy());
и создал пользовательский класс PolicyProvider
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;
}
}
Посмотрите, структура Cors позволяет вам добавить свою собственную логику для определения того, какие источники разрешены, и т.д. Это очень полезно, если вы публикуете REST API для внешнего мира и список людей (источников), которые могут получить доступ ваш сайт находится в контролируемой среде, такой как база данных. Теперь, если вы просто разрешаете все происхождение (что может быть не очень хорошая идея во всех случаях), вы можете просто сделать это в WebApiConfig.cs, чтобы включить CORS глобально:
config.EnableCors();
Подобно фильтрам и обработчикам в WebApi, вы также можете добавлять аннотации уровня или метода к вашим контроллерам, например:
[EnableCors("*, *, *, *")]
Обратите внимание, что атрибут EnableCors имеет конструктор, который принимает следующие параметры
- Список разрешенных источников
- Список разрешенных заголовков запросов
- Список разрешенных методов HTTP
- Список разрешенных заголовков ответов
Вы можете указать статически на каждом контроллере/конечной точке, которому разрешен доступ к какому ресурсу.
Обновление 06/24/2016:
Я должен упомянуть, что в моем Web.config есть следующее. Похоже, что это не могут быть значения по умолчанию для всех.
<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>
Источник: Microsoft
Ответ 5
Ни один из этих ответов не работал у меня, но следующие настройки webconfig. Две настройки для меня устанавливали Access-Control-Allow-Headers
на Content-Type
и комментировали строку, которая удаляет OPTIONSVerbHandler
:
<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>
Ответ 6
Это может быть красная селедка. Недавно я получил CORS, отлично работая, не пробираясь ни по одному из обручей, которые вы делаете.
Это было сделано с использованием комбинации Thinktecture.IdentityModel nuget и, что еще важнее... СНЯТИЕ всех ссылок на WebDAV. Это включает удаление модуля webdav из IIS и обеспечение следующих строк в вашей веб-конфигурации:
<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>
Затем вы можете просто использовать thinktecture, чтобы настроить CORS из вашего Global.asax, используя статический класс следующим образом:
public class CorsConfig
{
public static void RegisterCors(HttpConfiguration httpConfiguration)
{
var corsConfig = new WebApiCorsConfiguration();
corsConfig.RegisterGlobal(httpConfiguration);
corsConfig.ForAllResources().AllowAllOriginsAllMethodsAndAllRequestHeaders();
}
}
ИСТОЧНИК: http://brockallen.com/2012/06/28/cors-support-in-webapi-mvc-and-iis-with-thinktecture-identitymodel/
Ответ 7
На самом деле, я только что "упорядочил" принятый ответ
Не стесняйтесь поправлять меня
protected void Application_BeginRequest()//Put it inside global.asax[you might also need, inside web.config:<modules runAllManagedModulesForAllRequests="true">]
{
if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
Request.HttpMethod == "OPTIONS")
{
Response.Headers.Set("Allow", "GET, POST");//normally you only need get and post http method but you can append others also
Response.Headers.Set("Access-Control-Allow-Origin", "*");//its advised to add only the urls(i think its comma seperated)(https://example.com,https://*.example.com) you want to allow here
Response.Headers.Set("Access-Control-Allow-Headers", "*");//put here those Headers that you are going to need to pass(like ContentType) in the frontend
Response.End();
}
}