catch all unhandled exceptions in ASP.NET Web Api

C#asp.net Web-ApiUnhandled Exception

C# Problem Overview


How do I catch all unhandled exceptions that occur in ASP.NET Web Api so that I can log them?

So far I have tried:

  • Create and register an ExceptionHandlingAttribute
  • Implement an Application_Error method in Global.asax.cs
  • Subscribe to AppDomain.CurrentDomain.UnhandledException
  • Subscribe to TaskScheduler.UnobservedTaskException

The ExceptionHandlingAttribute successfully handles exceptions that are thrown within controller action methods and action filters, but other exceptions are not handled, for example:

  • Exceptions thrown when an IQueryable returned by an action method fails to execute
  • Exceptions thrown by a message handler (i.e. HttpConfiguration.MessageHandlers)
  • Exceptions thrown when creating a controller instance

Basically, if an exception is going to cause a 500 Internal Server Error to be returned to the client, I want it logged. Implementing Application_Error did this job well in Web Forms and MVC - what can I use in Web Api?

C# Solutions


Solution 1 - C#

This is now possible with WebAPI 2.1 (see the What's New):

Create one or more implementations of IExceptionLogger. For example:

public class TraceExceptionLogger : ExceptionLogger
{
    public override void Log(ExceptionLoggerContext context)
    {
        Trace.TraceError(context.ExceptionContext.Exception.ToString());
    }
}

Then register with your application's HttpConfiguration, inside a config callback like so:

config.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());

or directly:

GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());

Solution 2 - C#

To answer my own question, this isn't possible!

Handling all exceptions that cause internal server errors seems like a basic capability Web API should have, so I have put in a request with Microsoft for a Global error handler for Web API:

https://aspnetwebstack.codeplex.com/workitem/1001

If you agree, go to that link and vote for it!

In the meantime, the excellent article ASP.NET Web API Exception Handling shows a few different ways to catch a few different categories of error. It's more complicated than it should be, and it doesn't catch all interal server errors, but it's the best approach available today.

Update: Global error handling is now implemented and available in the nightly builds! It will be released in ASP.NET MVC v5.1. Here's how it will work: https://aspnetwebstack.codeplex.com/wikipage?title=Global%20Error%20Handling

Solution 3 - C#

The Yuval's answer is for customizing responses to unhandled exceptions caught by Web API, not for logging, as noted on the linked page. Refer to the When to Use section on the page for details. The logger is always called but the handler is called only when a response can be sent. In short, use the logger to log and the handler to customize the response.

By the way, I am using assembly v5.2.3 and the ExceptionHandler class does not have the HandleCore method. The equivalent, I think, is Handle. However, simply subclassing ExceptionHandler (as in Yuval's answer) does not work. In my case, I have to implement IExceptionHandler as follows.

internal class OopsExceptionHandler : IExceptionHandler
{
    private readonly IExceptionHandler _innerHandler;

    public OopsExceptionHandler (IExceptionHandler innerHandler)
    {
        if (innerHandler == null)
            throw new ArgumentNullException(nameof(innerHandler));

        _innerHandler = innerHandler;
    }

    public IExceptionHandler InnerHandler
    {
        get { return _innerHandler; }
    }

    public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
    {
        Handle(context);

        return Task.FromResult<object>(null);
    }

    public void Handle(ExceptionHandlerContext context)
    {
        // Create your own custom result here...
        // In dev, you might want to null out the result
        // to display the YSOD.
        // context.Result = null;
        context.Result = new InternalServerErrorResult(context.Request);
    }
}

Note that, unlike the logger, you register your handler by replacing the default handler, not adding.

config.Services.Replace(typeof(IExceptionHandler),
    new OopsExceptionHandler(config.Services.GetExceptionHandler()));

Solution 4 - C#

You can also create a global exception handler by implementing the IExceptionHandler interface (or inherit the ExceptionHandler base class). It will be the last to be called in the execution chain, after all registered IExceptionLogger:

> The IExceptionHandler handles all unhandled exceptions from all > controllers. This is the last in the list. If an exception occurs, the > IExceptionLogger will be called first, then the controller > ExceptionFilters and if still unhandled, the IExceptionHandler > implementation.

public class OopsExceptionHandler : ExceptionHandler
{
    public override void HandleCore(ExceptionHandlerContext context)
    {
        context.Result = new TextPlainErrorResult
        {
            Request = context.ExceptionContext.Request,
            Content = "Oops! Sorry! Something went wrong."        
        };
    }

    private class TextPlainErrorResult : IHttpActionResult
    {
        public HttpRequestMessage Request { get; set; }

        public string Content { get; set; }

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = 
                             new HttpResponseMessage(HttpStatusCode.InternalServerError);
            response.Content = new StringContent(Content);
            response.RequestMessage = Request;
            return Task.FromResult(response);
        }
    }
}

More on that here.

Solution 5 - C#

You may have existing try-catch blocks that you're not aware of.

I thought my new global.asax.Application_Error method wasn't being consistently called for unhandled exceptions in our legacy code.

Then I found a few try-catch blocks in the middle of the call stack that called Response.Write on the Exception text. That was it. Dumped the text on the screen then killed the exception stone dead.

So the exceptions were being handled, but the handling was doing nothing useful. Once I removed those try-catch blocks the exceptions propagated to the Application_Error method as expected.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJoe DaleyView Question on Stackoverflow
Solution 1 - C#decatesView Answer on Stackoverflow
Solution 2 - C#Joe DaleyView Answer on Stackoverflow
Solution 3 - C#Duoc TranView Answer on Stackoverflow
Solution 4 - C#Yuval ItzchakovView Answer on Stackoverflow
Solution 5 - C#ResourceView Answer on Stackoverflow