catch all unhandled exceptions in ASP.NET Web Api

63,613

Solution 1

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

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

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

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.

Share:
63,613
user3156301
Author by

user3156301

Updated on May 12, 2020

Comments

  • user3156301
    user3156301 over 3 years

    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?

  • Sonic Soul
    Sonic Soul about 10 years
    seems like a reason to use controller for ajax calls instead of web api.. the boundaries are already blurry.. although if ELMAH is able to capture it, maybe there is a way
  • decates
    decates almost 10 years
    Global error handling now been added in Web API 2.1. See my answer for more details.
  • decates
    decates over 9 years
    @NeilBarnwell Yes, Web API 2.1 corresponds to System.Web.Http assembly version 5.1.0. So you need this version or above to use the solution described here. See the nuget package versions
  • Avner
    Avner over 8 years
    Certain 500 errors still don't get caught by this, eg. HttpException - the remote host closed the connection. Is there still a place for global.asax Application_Error to handle errors outside web api processing?
  • Rajiv
    Rajiv almost 7 years
    This is a great solution, this should be the accepted solution to 'catch or log all errors'. I could have never figured out what why it didn't work for me when I was just extending ExceptionHandler.
  • Gustyn
    Gustyn almost 7 years
    Great solution. Once the MVC pipeline has been loaded for a request this works great. IIS still handles the exceptions until then, including when spinning up OWIN in startup.cs. However, at some point after spin up finishes processing startup.cs it does it's job wonderfully.
  • Rocklan
    Rocklan over 6 years
    I love how detailed the official doco is on msdn and what 99% of developers really want is just the 8 lines of code to log errors.
  • ThunD3eR
    ThunD3eR over 3 years
    Just curious, where is this registerd?
  • Robert
    Robert almost 3 years
    I'm sure it still doesn't catch all the 500 errors: for example, if an error is thrown inside the Invoke method of a custom Owin Middleware!
  • Kevin Dimey
    Kevin Dimey over 2 years
    Handler can be registered with your dependency injection framework