killswitch
killswitch

Reputation: 365

How to configure multiple exception handlers

I am trying to configure my middleware pipeline to use 2 different exception handlers to handle the same exception. For example, I'm trying to have both my custom handler and in-built DeveloperExceptionPageMiddleware as follows:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();                
        app.ConfigureCustomExceptionHandler();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");                               
        app.ConfigureCustomExceptionHandler();            
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseCookiePolicy();
    app.UseAuthentication();
    app.UseMvcWithDefaultRoute();
}

My objective is to have the custom handler do its own thing (logging, telemetry, etc), and then pass on (next()) to the other in-built handler which displays a page. My custom handler looks like this:

public static class ExceptionMiddlewareExtensions
{
    public static void ConfigureCustomExceptionHandler(this IApplicationBuilder app)
    {            
        app.UseExceptionHandler(appError =>
        {
            appError.Use(async (context, next) =>
            {                    
                var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
                if (contextFeature != null)
                {
                    //log error / do custom stuff

                    await next();
                }
            });
        });
    }
}

I cannot get CustomExceptionHandler to pass on processing to the next middleware. I get the following page instead:

404 error:

enter image description here

I tried switching around the order, but then the developer exception page takes over and the custom exception handler is not called.

Is what I'm trying to do possible at all?

Update:

The solution was to take Simonare's original suggestion and re-throw the exception in the Invoke method. I also had to remove any type of response-meddling by replacing the following in HandleExceptionAsync method:

context.Response.ContentType = "application/json"; context.Response.StatusCode = (int)code; return context.Response.WriteAsync(result);

with:

return Task.CompletedTask;

Upvotes: 7

Views: 3325

Answers (2)

Alex from Jitbit
Alex from Jitbit

Reputation: 60546

Here's a very simple version of how to use custom exception handling logic WITH the built-in ASP.NET Core error page at the same time:

app.UseExceptionHandler("/Error"); //use standard error page
app.Use(async (context, next) => //simple one-line middleware
{
    try
    {
        await next.Invoke(); //attempt to run further application code
    }
    catch (Exception ex) //something went wrong
    {
        //log exception, notify the webmaster, etc.
        Log_Exception_And_Send_Email_or_Whatever(ex);
        //re-throw the exception so it's caught by the outer "UseExceptionHandler"
        throw;
    }
});

P.S. Uhmm, I added an explicit language: c# hint to my answer but syntax highlighting still does not see catch as a keyword... Interesting.

Upvotes: 3

Derviş Kayımbaşıoğlu
Derviş Kayımbaşıoğlu

Reputation: 30545

Instead of calling two different Exception Handling Middleware, you may consider to add logging under your Home/Error

[AllowAnonymous]
public IActionResult Error()
{
    //log your error here
    return View(new ErrorViewModel 
        { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}

alternatively, you can use custom Expception Handling Middleware

public class ErrorHandlingMiddleware
{
    private readonly RequestDelegate _next;

    public ErrorHandlingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context, IHostingEnvironment env)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            if (!context.Response.HasStarted)
                await HandleExceptionAsync(context, ex, env);
            throw;
        }
    }

    private Task HandleExceptionAsync(HttpContext context, Exception exception, IHostingEnvironment env)
    {
        var code = HttpStatusCode.InternalServerError; // 500 if unexpected
        var message = exception.Message;

        switch (exception)
        {
            case NotImplementedException _:
                code = HttpStatusCode.NotImplemented; 
                break;
            //other custom exception types can be used here
            case CustomApplicationException cae: //example
                code = HttpStatusCode.BadRequest;
                break;
        }

        Log.Write(code == HttpStatusCode.InternalServerError ? LogEventLevel.Error : LogEventLevel.Warning, exception, "Exception Occured. HttpStatusCode={0}", code);


        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)code;
        return Task.Completed;
    }
}

and Simply Register it inside IApplicationBuilder Method

  public void Configure(IApplicationBuilder app)
  {
        app.UseMiddleware<ErrorHandlingMiddleware>();
  }

Upvotes: 2

Related Questions