28

How comes that a custom ExceptionHandler is never called and instead a standard response (not the one I want) is returned?

Registered like this

config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());

and implemented like this

public class GlobalExceptionHandler : ExceptionHandler
{
    public override void Handle(ExceptionHandlerContext context)
    {
        context.Result = new ExceptionResponse
        {
            statusCode = context.Exception is SecurityException ? HttpStatusCode.Unauthorized : HttpStatusCode.InternalServerError,
            message = "An internal exception occurred. We'll take care of it.",
            request = context.Request
        };
    }
}

public class ExceptionResponse : IHttpActionResult
{
    public HttpStatusCode statusCode { get; set; }
    public string message { get; set; }
    public HttpRequestMessage request { get; set; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(statusCode);
        response.RequestMessage = request;
        response.Content = new StringContent(message);
        return Task.FromResult(response);
    }
}

and thrown like this (test)

throw new NullReferenceException("testerror");

in a controller or in a repository.

UPDATE

I do not have another ExceptionFilter.

I found a trigger for this behavior:

Given URL

GET http://localhost:XXXXX/template/lock/someId

sending this header, my ExceptionHandler works

Host: localhost:XXXXX

sending this header, it doesn't work and the built-in handler returns the error instead

Host: localhost:XXXXX
Origin: http://localhost:YYYY

This might be an issue with CORS requests (I use the WebAPI CORS package globally with wildcards) or eventually my ELMAH logger. It also happens when hosted on Azure (Websites), though the built-in error handler is different.

Any idea how to fix this?

Benjamin E.
  • 5,042
  • 5
  • 38
  • 65
  • Do you happen to have an exception filter too? Also can you share how your controller or repository code looks like...we want to make sure that you are not catching it somewhere and converting it to HttpResponseException or something in which case exception handler would not get invoked. – Kiran Mar 04 '14 at 11:17
  • @KiranChalla: Interesting update above, thanks! – Benjamin E. Mar 04 '14 at 15:05

2 Answers2

37

Turns out the default only handles outermost exceptions, not exceptions in repository classes. So below has to be overridden as well:

public virtual bool ShouldHandle(ExceptionHandlerContext context)
{
    return context.ExceptionContext.IsOutermostCatchBlock;
}

UPDATE 1

WebAPI v2 does not use IsOutermostCatchBlock anymore. Anyway nothing changes in my implementation, since the new code in ShouldHandle still prevents my Error Handler. So I'm using this and my Error Handler gets called once. I catch errors in Controllers and Repositories this way.

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

UPDATE 2

Since this question got so much attention, please be aware that the current solution is the one linked by @JustAMartin in the comments below.

Benjamin E.
  • 5,042
  • 5
  • 38
  • 65
  • 3
    Notice also this one: http://stackoverflow.com/questions/22038800/can-anyone-explain-the-work-flow-of-iexceptionhandler-with-sample-client-applica It seems they have replaced IsOutermostCatchBlock with IsTopLevel – JustAMartin Jun 12 '14 at 15:57
  • Thanks for the update @Martin. Still it's the same, don't know why Controller errors don't get handled with the standard implementation. – Benjamin E. Jun 25 '14 at 08:56
  • 7
    I just found a solution for exception handling with CORS, see if this might be helpful for you: http://stackoverflow.com/questions/24189315/exceptions-in-asp-net-web-api-custom-exception-handler-never-reach-top-level-whe/24634485#24634485 It seems even ASP.NET team is not using ShouldHandle anymore but they don't mention it anywhere, thus causing the confusion. I suggest you to forget the ExceptionHandler base class and implement your solution as ASP.NET team does: https://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Http/ExceptionHandling/DefaultExceptionHandler.cs – JustAMartin Jul 08 '14 at 14:31
  • Thank you very much! This worked for me, the only difference is I used public override instead of public virtual. – ajpetersen Sep 30 '15 at 13:06
  • public override bool ShouldHandle(ExceptionHandlerContext context) => true; does the trick. Thank you! – FelipeDrumond Sep 01 '19 at 08:50
8

The real culprit here is CorsMessageHandler inserted by EnableCors method in message processing pipline. The catch block intercept any exception and convert into a response before it can reach the HTTPServer try-catch block and ExceptionHandler logic can be invoked

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    CorsRequestContext corsRequestContext = request.GetCorsRequestContext();
    HttpResponseMessage result;
    if (corsRequestContext != null)
    {
    try
    {
        if (corsRequestContext.IsPreflight)
        {
        result = await this.HandleCorsPreflightRequestAsync(request, corsRequestContext, cancellationToken);
        return result;
        }
        result = await this.HandleCorsRequestAsync(request, corsRequestContext, cancellationToken);
        return result;
    }
    catch (Exception exception)
    {
        result = CorsMessageHandler.HandleException(request, exception);
        return result;
    }
    }
    result = await this.<>n__FabricatedMethod3(request, cancellationToken);
    return result;
}
Sarvesh Gupta
  • 81
  • 1
  • 1
  • 1
    Workarounds can be found on http://stackoverflow.com/questions/24189315/exceptions-in-asp-net-web-api-custom-exception-handler-never-reach-top-level-whe/24634485#24634485 – 0xced Jun 17 '16 at 09:27