9

In my C# Web API, I'm trying to add a global exception handler. I've been using a custom global ExceptionFilterAttribute to handle the exception and return a HttpResponseMessage:

public override void OnException(HttpActionExecutedContext context)
{
    ...

    const string message = "An unhandled exception was raised by the Web API.";
    var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.InternalServerError)
    {
        Content = new StringContent(message),
        ReasonPhrase = message
    };
    context.Response = httpResponseMessage;
}

This has worked fine for handling exceptions thrown at the controller level.

However, during development we had an error thrown from our OWIN startup file due to a database connection issue, however, a standard IIS exception was returned, instead of going through the global exception handler, and the full HTML was returned to our API consumer.

I've tried a few different approaches to catch exceptions thrown in my OWIN startup:

Custom ApiControllerActionInvoker:

public class CustomActionInvoker : ApiControllerActionInvoker
{
    public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        var result = base.InvokeActionAsync(actionContext, cancellationToken);

        if (result.Exception != null && result.Exception.GetBaseException() != null)
        {
            ...
        }

        return result;
    }
}

Custom ExceptionHandler:

public class CustomExceptionHandler : ExceptionHandler
{
    public override void Handle(ExceptionHandlerContext context)
    {
        ...

        base.Handle(context);
    }

    public override bool ShouldHandle(ExceptionHandlerContext context)
    {
        return true;
    }
}

Custom OwinMiddleware component:

public class CustomExceptionMiddleware : OwinMiddleware
{
    public CustomExceptionMiddleware(OwinMiddleware next) : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        try
        {
            await Next.Invoke(context);
        }
        catch (Exception ex)
        {
            ...
        }
    }
}

And finally just using Application_Error:

protected void Application_Error(object sender, EventArgs e)
{
    ...
}

But nothing seems to catch the exception.

Does anyone know of a way to catch the exception and return a HttpResponseMessage? Or if any of the approaches I've already tried should have worked?

Any help much appreciated.

Dan Ellis
  • 5,537
  • 8
  • 47
  • 73

1 Answers1

5

I have an application that does this correctly. In my case I wrote a middleware class that always returns a message telling the caller that the service is unavailable because there was an error during startup. This class is called FailedSetupMiddleware in my solution. The outline of it looks like this:

public class FailedSetupMiddleware
{
    private readonly Exception _exception;

    public FailedSetupMiddleware(Exception exception)
    {
        _exception = exception;
    }

    public Task Invoke(IOwinContext context, Func<Task> next)
    {
        var message = ""; // construct your message here
        return context.Response.WriteAsync(message);
    }
}

In my Configuration class I have a try...catch block that configures the OWIN pipeline with only the FailedSetupMiddleware in the case where an exception was thrown during configuration.

My OWIN startup class looks like this:

[assembly: OwinStartup(typeof(Startup))]

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        try
        {
            //
            // various app.Use() statements here to configure
            // OWIN middleware
            //
        }
        catch (Exception ex)
        {
          app.Use(new FailedSetupMiddleware(ex).Invoke);
        }
    }
}
bikeman868
  • 2,236
  • 23
  • 30
  • Once the exception is thrown this way how do you control retry logic without recycling the application pool? Going back to the OP if an OWIN statement relies on a DB connection and that DB connect has a short outage your solution will end the exception cleanly and won't re-fire the "startup" class until the application pool is recycled. This is less than ideal; however, if you re-throw the exception the OWIN startup class will re-run on a subsequent page refresh. The only problem is the user won't get a clean error page. – spyder1329 Sep 02 '22 at 19:50
  • With this implementation, if the application throws an exception during the OWIN pipeline configuration phase, then it remains permanently in the state where it returns the exception that was thrown for all subsequent requests. This is by design. Without this try/catch in place it is very hard to debug issues with pipeline configuration. – bikeman868 Sep 06 '22 at 17:34