首页 > 解决方案 > 是否可以使用带有 ExceptionHandler 选项的 UseExceptionHandler() 来配置“专门处理 web api 请求”?

问题描述

我想返回有关错误的特定 json 信息。

我有自定义中间件的解决方案,但我不明白如何使用标准 ExceptionHandler 选项做同样的事情:

我正在努力:

app.UseExceptionHandler(
    new ExceptionHandlerOptions() {
        ExceptionHandlingPath=new PathString("/Error"),
        ExceptionHandler = async context =>
        {
            var ex = context.Features.Get<IExceptionHandlerFeature>().Error;
            var originalFeature = context.Features.Get<IExceptionHandlerPathFeature>();
            bool isApiCall = false;
            if (originalFeature!=null && originalFeature.Path!=null && originalFeature.Path.Contains("Api/")) // TODO: regex
            {
                isApiCall = true;
            }

            if (isApiCall)
            {
                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync(AspCoreManager.GetErrorActionJson(ex, "", true));
            }
            else
            {
                await /* ???  how to get the "_next" delegate (from pipeline) or how to abort a pipeline and response with an "/Error" page */;
            }
        }
    });

所以我不明白如何返回标准处理——调用“/Error”页面。

这是自定义中间件,可以完成我需要的所有工作,但我有一个神奇的_next委托可以完成所有工作:

// modified and simplified https://github.com/aspnet/Diagnostics/blob/master/src/Microsoft.AspNetCore.Diagnostics/ExceptionHandler/ExceptionHandlerMiddleware.cs
public class MyExceptionHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ExceptionHandlerOptions _options;
    private readonly ILogger _logger;
    private readonly Func<object, Task> _clearCacheHeadersDelegate;
    private readonly DiagnosticSource _diagnosticSource;
    private readonly ApplicationSettings applicationSettings;

    public MyExceptionHandlerMiddleware(
        RequestDelegate next,
        ILoggerFactory loggerFactory,
        IOptions<ExceptionHandlerOptions> options,
        DiagnosticSource diagnosticSource,

        )
    {
        _next = next;
        _options = options.Value;
        _logger = loggerFactory.CreateLogger<ExceptionHandlerMiddleware>();
        _clearCacheHeadersDelegate = ClearCacheHeaders;
        _diagnosticSource = diagnosticSource;
        if (_options.ExceptionHandler == null)
        {
            _options.ExceptionHandler = _next;

        }
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            if (context.Response.HasStarted)
            {
                throw;
            }
            PathString originalPath = context.Request.Path;
            bool isApiCall = false;
            if (originalPath.HasValue && originalPath.Value.Contains("Api/")) 
            {
                isApiCall = true;
            }

            if (_options.ExceptionHandlingPath.HasValue)
            {
                context.Request.Path = _options.ExceptionHandlingPath;
            }
            try
            {
                context.Response.Clear();
                var exceptionHandlerFeature = new ExceptionHandlerFeature()
                {
                    Error = ex,
                    Path = originalPath.Value,
                };
                context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);
                context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);
                context.Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; // 500
                context.Response.OnStarting(_clearCacheHeadersDelegate, context.Response);
                if (isApiCall)
                {
                    context.Response.ContentType = "application/json";
                    await context.Response.WriteAsync(AspCoreManager.GetErrorActionJson(ex));
                }
                else
                {
                    await _options.ExceptionHandler(context);
                }

                return;
            }
            catch (Exception ex2)
            {
                // Suppress secondary exceptions
            }
            finally
            {
                context.Request.Path = originalPath;
            }
            throw; // Re-throw the original if we couldn't handle it
        }
    }

    private Task ClearCacheHeaders(object state)
    {
        var response = (HttpResponse)state;
        response.Headers[HeaderNames.CacheControl] = "no-cache";
        response.Headers[HeaderNames.Pragma] = "no-cache";
        response.Headers[HeaderNames.Expires] = "-1";
        response.Headers.Remove(HeaderNames.ETag);
        return Task.CompletedTask;
    }
}

标签: c#asp.net-coreasp.net-core-webapi

解决方案


您可能可以通过以下方式实现两全其美:

const string errorPath = "/Error";

app.UseExceptionHandler(errorPath);
app.Use(async (ctx, next) =>
{
    if (ctx.Request.Path == errorPath)
    {
        var ex = ctx.Features.Get<IExceptionHandlerFeature>().Error;
        var originalFeature = ctx.Features.Get<IExceptionHandlerPathFeature>();

        if (originalFeature != null && originalFeature.Path != null && originalFeature.Path.Contains("Api/")) // TODO: regex
        {
            ctx.Response.ContentType = "application/json";
            await ctx.Response.WriteAsync(AspCoreManager.GetErrorActionJson(ex));
            return;
        }
    }

    // Request.Path is not for /Error *or* this isn't an API call.
    await next();
});

在这个例子中,我们重用所有现有UseExceptionHandler的日志记录、路径重写等逻辑,然后使用一个额外的中间件来拦截对 的调用/Error,检查它是否是 API 调用,然后做出相应的反应。


推荐阅读