Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to use UseExceptionHandler() with ExceptionHandler option for configuring "handle web api requests specifically"?

I want to return specific json information on the error.

I have the solution with custom middleware, but I can't understand how to do the same with standard ExceptionHandler option:

I'm trying:

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 */;
            }
        }
    });

So I do not understand how to return to the standard processing - call the "/Error" page.

And this is custom middleware that do all what I need, but there I have the magic _next delegate that do all the job:

// 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;
    }
}
like image 454
Roman Pokrovskij Avatar asked Apr 02 '19 10:04

Roman Pokrovskij


1 Answers

You might be able to achieve the best of both worlds here with something like the following:

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

In this example, we reuse all of the existing UseExceptionHandler logic for logging, path-rewrite, etc, and then use an additional middleware that intercepts calls to /Error, checks whether it's an API call and then reacts accordingly.

like image 82
Kirk Larkin Avatar answered Nov 15 '22 09:11

Kirk Larkin