如何在ASPNET.Core Web应用程序中发送带有CORS标头的HTTP 4xx-5xx响应? [英] How to send an HTTP 4xx-5xx response with CORS headers in an ASPNET.Core web app?

查看:53
本文介绍了如何在ASPNET.Core Web应用程序中发送带有CORS标头的HTTP 4xx-5xx响应?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个标准的ASP.NET Core 2 Web App,它充当REST/WebApi.对于我的一个端点,当用户提供错误的搜索/过滤器查询字符串参数时,我返回HTTP 400.

I have a standard ASP.NET Core 2 Web App acting as a REST/WebApi. For one of my endpoints I return an HTTP 400 when the user provides bad search/filter querystring arguments.

与POSTMAN一起使用效果很好.但是,当我尝试使用我的SPA应用程序对此进行测试(实际上,该应用程序现在正在跨域并因此执行CORS请求)时,Chrome出现了故障.

Works great with POSTMAN. But when I try and test this with my SPA app (which in effect is now crossing domains and thus doing a CORS request), I get a failure in Chrome.

在对返回HTTP 200响应的端点进行CORS请求时,一切正常.

When doing a CORS request to an endpoint that returns an HTTP 200 response, all works fine.

我的错误处理似乎没有考虑CORS的内容(即未添加任何CORS标头),并且未包含该信息.

It looks like my error handling is NOT taking into consideration the CORS stuff (i.e. not adding any CORS headers) and isn't including that.

我猜我正在弄乱响应有效负载管道内容.

问:有没有一种方法可以在自定义错误处理中更正返回任何CORS标头信息,而无需对标头进行硬编码,而是使用在Startup.cs中的Configure/ConfigureServices方法中设置的标头内容? strong>

Q: Is there a way to correct return any CORS header information in a custom Error Handling without hardcoding the header but instead using the headers stuff that was setup in the Configure/ConfigureServices methods in Startup.cs?

Pseduo代码.

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在传递给您自己的中间件功能之前被清除,如

In the ExceptionHandler middleware, the Response is cleared before being passed into your own middleware function, as can be seen in the source:

try
{
    await _next(context);
}
catch (Exception ex)
{
    // ...
    context.Response.Clear();

    // ...
    await _options.ExceptionHandler(context);

    // ..
}

当然,这意味着可能已经针对CORS设置的任何响应标头也都是

Of course, this means that any response headers that might have been set in respect to CORS are also being cleared.

以下代码可插入一般的CORS系统,并且我相信确实可以满足您对使用ConfigureServices中的配置的要求:

The following code plugs in to the general CORS system, and I believe does appear to mostly satisfy your requirement that the configuration from ConfigureServices can be used:

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(如我的示例),它将使用默认策略(如果已设置).

GetPolicyAsync takes the name of a policy as the second parameter - If this is null (as in my example), it will use the default policy, if this has been set up.

为了保持重点,我没有在代码示例中包含空检查或任何内容,但是这种方法在我构建的测试项目中有效.

I've not included null-checks or anything in the code example, in order to keep it focussed, but this approach is working in a test project I have built.

您在示例代码中没有使用命名策略,但是可以使用以下方法切换到一个命名策略:

You're not using a named policy in your example code, but you can switch over to one using the following:

.AddCors(corsOptions => corsOptions.AddPolicy(
    "Default",
    corsPolicyBuilder => corsPolicyBuilder
        .AllowAnyOrigin()
        .AllowAnyHeader()
        .AllowAnyMethod()));

这使用了AddPolicy-我在注释中提到了AddDefaultPolicy,但是看起来这不是当前版本中的,因此尚不可用.通过上述更改,您可以像这样调用UseCors:

This uses AddPolicy - I mentioned AddDefaultPolicy in the comments, but it looks like this is not in the current release and so not available yet. With the changes above, you can just call UseCors like so:

app.UseCors("Default");

最后的更改是将异常处理代码中的内容更新为以下内容:

The final change is to update to the following in your exception handling code:

await corsPolicyProvider.GetPolicyAsync(httpContext, "Default");

为此,最好使用某种const字符串,特别是因为它很可能都是在同一文件中运行的.此处的主要更改不再是尝试使用默认的命名策略,因为我正在查看GitHub上尚未发布的源代码的当前版本.

You'd be better off using some sort of const string for this, especially as it's likely all running from the same file. The main change here is no longer attempting to use the default named policy, as I was looking at the current version of the source code on GitHub that is yet to be released.

这篇关于如何在ASPNET.Core Web应用程序中发送带有CORS标头的HTTP 4xx-5xx响应?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆