Web API OWIN startup exception handling

2019-04-07 17:03发布

问题:

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.

回答1:

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