如何在 ASPNET.Core 网络应用程序中使用 CORS headers 发送 HTTP 4xx-5xx 响应?
How to send an HTTP 4xx-5xx response with CORS headers in an ASPNET.Core web app?
我有一个标准的 ASP.NET Core 2 Web App 作为 REST/WebApi。对于我的一个端点,当用户提供错误的 search/filter 查询字符串参数时,我 return 一个 HTTP 400
。
与 POSTMAN 配合得很好。但是,当我尝试使用我的 SPA 应用程序对此进行测试时(实际上它现在正在跨域并因此执行 CORS 请求),我在 Chrome.
中遇到了失败
当向 return 响应 HTTP 200
的端点发出 CORS 请求时,一切正常。
看起来我的错误处理没有考虑 CORS 的东西(即没有添加任何 CORS headers)并且不包括它。
我猜我弄乱了响应负载管道的东西。
问:有没有一种方法可以在自定义错误处理中更正 return 任何 CORS header 信息而无需对 header 进行硬编码,而是使用 headers 在 Startup.cs
?
中的 Configure/ConfigureServices
方法中设置的内容
伪代码..
public void ConfigureServices(IServiceCollection services)
{
... snip ...
services.AddMvcCore()
.AddAuthorization()
.AddFormatterMappings()
.AddJsonFormatters(options =>
{
options.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.Formatting = Formatting.Indented;
options.DateFormatHandling = DateFormatHandling.IsoDateFormat;
options.NullValueHandling = NullValueHandling.Ignore;
options.Converters.Add(new StringEnumConverter());
})
.AddCors(); // REF: https://docs.microsoft.com/en-us/aspnet/core/security/cors#setting-up-cors
... snip ...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
... snip ...
app.UseExceptionHandler(options => options.Run(async httpContext => await ExceptionResponseAsync(httpContext, true)));
app.UseCors(builder => builder//.WithOrigins("http://localhost:52383", "http://localhost:49497")
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
... snip ...
}
private static async Task ExceptionResponseAsync(HttpContext httpContext, bool isDevelopmentEnvironment)
{
var exceptionFeature = httpContext.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionFeature == null)
{
// An unknow and unhandled exception occured. So this is like a fallback.
exceptionFeature = new ExceptionHandlerFeature
{
Error = new Exception("An unhandled and unexpected error has occured. Ro-roh :~(.")
};
}
await ConvertExceptionToJsonResponseAsyn(exceptionFeature,
httpContext.Response,
isDevelopmentEnvironment);
}
private static Task ConvertExceptionToJsonResponseAsyn(IExceptionHandlerPathFeature exceptionFeature,
HttpResponse response,
bool isDevelopmentEnvironment)
{
if (exceptionFeature == null)
{
throw new ArgumentNullException(nameof(exceptionFeature));
}
if (response == null)
{
throw new ArgumentNullException(nameof(response));
}
var exception = exceptionFeature.Error;
var includeStackTrace = false;
var statusCode = HttpStatusCode.InternalServerError;
var error = new ApiError();
if (exception is ValidationException)
{
statusCode = HttpStatusCode.BadRequest;
foreach(var validationError in ((ValidationException)exception).Errors)
{
error.AddError(validationError.PropertyName, validationError.ErrorMessage);
}
}
else
{
// Final fallback.
includeStackTrace = true;
error.AddError(exception.Message);
}
if (includeStackTrace &&
isDevelopmentEnvironment)
{
error.StackTrace = exception.StackTrace;
}
var json = JsonConvert.SerializeObject(error, JsonSerializerSettings);
response.StatusCode = (int)statusCode;
response.ContentType = JsonContentType;
// response.Headers.Add("Access-Control-Allow-Origin", "*"); <-- Don't want to hard code this.
return response.WriteAsync(json);
}
干杯!
在ExceptionHandler
中间件中,Response
在传递到你自己的中间件函数之前被清除,在source:
中可以看到
try
{
await _next(context);
}
catch (Exception ex)
{
// ...
context.Response.Clear();
// ...
await _options.ExceptionHandler(context);
// ..
}
当然,这意味着可能针对 CORS 设置的任何响应 headers 也是 being cleared。
下面的代码插入到通用的 CORS 系统中,我相信看起来确实大部分满足了您的要求,即可以使用 ConfigureServices
中的配置:
var corsService = httpContext.RequestServices.GetService<ICorsService>();
var corsPolicyProvider = httpContext.RequestServices.GetService<ICorsPolicyProvider>();
var corsPolicy = await corsPolicyProvider.GetPolicyAsync(httpContext, null);
corsService.ApplyResult(
corsService.EvaluatePolicy(httpContext, corsPolicy),
httpContext.Response);
GetPolicyAsync
将策略名称作为第二个参数 - 如果这是 null(如我的示例),它将使用默认策略,如果已设置。
我没有在代码示例中包含 null-checks 或任何内容,以保持重点,但这种方法在我构建的测试项目中有效。
这种方法深受 Microsoft.AspNetCore.Mvc.Cors 中 CorsAuthorizationFilter 源代码的影响。
编辑:您没有在示例代码中使用命名策略,但您可以使用以下方法切换到一个命名策略:
.AddCors(corsOptions => corsOptions.AddPolicy(
"Default",
corsPolicyBuilder => corsPolicyBuilder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()));
这使用 AddPolicy
- 我在评论中提到了 AddDefaultPolicy
,但看起来这不在当前版本中,因此尚不可用。通过上述更改,您可以像这样调用 UseCors
:
app.UseCors("Default");
最后的更改是在您的异常处理代码中更新为以下内容:
await corsPolicyProvider.GetPolicyAsync(httpContext, "Default");
你最好为此使用某种 const 字符串,尤其是因为它可能全部 运行 来自同一个文件。这里的主要变化是不再尝试使用默认的命名策略,因为我正在查看 GitHub 上尚未发布的当前版本的源代码。
我有一个标准的 ASP.NET Core 2 Web App 作为 REST/WebApi。对于我的一个端点,当用户提供错误的 search/filter 查询字符串参数时,我 return 一个 HTTP 400
。
与 POSTMAN 配合得很好。但是,当我尝试使用我的 SPA 应用程序对此进行测试时(实际上它现在正在跨域并因此执行 CORS 请求),我在 Chrome.
中遇到了失败当向 return 响应 HTTP 200
的端点发出 CORS 请求时,一切正常。
看起来我的错误处理没有考虑 CORS 的东西(即没有添加任何 CORS headers)并且不包括它。
我猜我弄乱了响应负载管道的东西。
问:有没有一种方法可以在自定义错误处理中更正 return 任何 CORS header 信息而无需对 header 进行硬编码,而是使用 headers 在 Startup.cs
?
Configure/ConfigureServices
方法中设置的内容
伪代码..
public void ConfigureServices(IServiceCollection services)
{
... snip ...
services.AddMvcCore()
.AddAuthorization()
.AddFormatterMappings()
.AddJsonFormatters(options =>
{
options.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.Formatting = Formatting.Indented;
options.DateFormatHandling = DateFormatHandling.IsoDateFormat;
options.NullValueHandling = NullValueHandling.Ignore;
options.Converters.Add(new StringEnumConverter());
})
.AddCors(); // REF: https://docs.microsoft.com/en-us/aspnet/core/security/cors#setting-up-cors
... snip ...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
... snip ...
app.UseExceptionHandler(options => options.Run(async httpContext => await ExceptionResponseAsync(httpContext, true)));
app.UseCors(builder => builder//.WithOrigins("http://localhost:52383", "http://localhost:49497")
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
... snip ...
}
private static async Task ExceptionResponseAsync(HttpContext httpContext, bool isDevelopmentEnvironment)
{
var exceptionFeature = httpContext.Features.Get<IExceptionHandlerPathFeature>();
if (exceptionFeature == null)
{
// An unknow and unhandled exception occured. So this is like a fallback.
exceptionFeature = new ExceptionHandlerFeature
{
Error = new Exception("An unhandled and unexpected error has occured. Ro-roh :~(.")
};
}
await ConvertExceptionToJsonResponseAsyn(exceptionFeature,
httpContext.Response,
isDevelopmentEnvironment);
}
private static Task ConvertExceptionToJsonResponseAsyn(IExceptionHandlerPathFeature exceptionFeature,
HttpResponse response,
bool isDevelopmentEnvironment)
{
if (exceptionFeature == null)
{
throw new ArgumentNullException(nameof(exceptionFeature));
}
if (response == null)
{
throw new ArgumentNullException(nameof(response));
}
var exception = exceptionFeature.Error;
var includeStackTrace = false;
var statusCode = HttpStatusCode.InternalServerError;
var error = new ApiError();
if (exception is ValidationException)
{
statusCode = HttpStatusCode.BadRequest;
foreach(var validationError in ((ValidationException)exception).Errors)
{
error.AddError(validationError.PropertyName, validationError.ErrorMessage);
}
}
else
{
// Final fallback.
includeStackTrace = true;
error.AddError(exception.Message);
}
if (includeStackTrace &&
isDevelopmentEnvironment)
{
error.StackTrace = exception.StackTrace;
}
var json = JsonConvert.SerializeObject(error, JsonSerializerSettings);
response.StatusCode = (int)statusCode;
response.ContentType = JsonContentType;
// response.Headers.Add("Access-Control-Allow-Origin", "*"); <-- Don't want to hard code this.
return response.WriteAsync(json);
}
干杯!
在ExceptionHandler
中间件中,Response
在传递到你自己的中间件函数之前被清除,在source:
try
{
await _next(context);
}
catch (Exception ex)
{
// ...
context.Response.Clear();
// ...
await _options.ExceptionHandler(context);
// ..
}
当然,这意味着可能针对 CORS 设置的任何响应 headers 也是 being cleared。
下面的代码插入到通用的 CORS 系统中,我相信看起来确实大部分满足了您的要求,即可以使用 ConfigureServices
中的配置:
var corsService = httpContext.RequestServices.GetService<ICorsService>();
var corsPolicyProvider = httpContext.RequestServices.GetService<ICorsPolicyProvider>();
var corsPolicy = await corsPolicyProvider.GetPolicyAsync(httpContext, null);
corsService.ApplyResult(
corsService.EvaluatePolicy(httpContext, corsPolicy),
httpContext.Response);
GetPolicyAsync
将策略名称作为第二个参数 - 如果这是 null(如我的示例),它将使用默认策略,如果已设置。
我没有在代码示例中包含 null-checks 或任何内容,以保持重点,但这种方法在我构建的测试项目中有效。
这种方法深受 Microsoft.AspNetCore.Mvc.Cors 中 CorsAuthorizationFilter 源代码的影响。
编辑:您没有在示例代码中使用命名策略,但您可以使用以下方法切换到一个命名策略:
.AddCors(corsOptions => corsOptions.AddPolicy(
"Default",
corsPolicyBuilder => corsPolicyBuilder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()));
这使用 AddPolicy
- 我在评论中提到了 AddDefaultPolicy
,但看起来这不在当前版本中,因此尚不可用。通过上述更改,您可以像这样调用 UseCors
:
app.UseCors("Default");
最后的更改是在您的异常处理代码中更新为以下内容:
await corsPolicyProvider.GetPolicyAsync(httpContext, "Default");
你最好为此使用某种 const 字符串,尤其是因为它可能全部 运行 来自同一个文件。这里的主要变化是不再尝试使用默认的命名策略,因为我正在查看 GitHub 上尚未发布的当前版本的源代码。