46

I want to wire up exception handling in a middleware component, something like this:

public override async Task Invoke(IOwinContext context)
{
    try
    {
        await Next.Invoke(context);
    }
    catch (Exception ex)
    {
        // Log error and return 500 response
    }
}

However, some of the exceptions I would like to catch are being caught and converted to HttpErrorResponses by the Web API pipeline before I can get to them. In the process, I lose a lot of details about the errors, so I can't get useful stack traces when debugging etc (the debugger doesn't even stop when the exception is thrown - I have to manually step through the code and see where it fails...).

I tried adding a custom exception handler with the following implementation:

public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
    var owinContext = context.Request.GetOwinContext();
    owinContext.Set(Constants.ContextKeys.Exception, context.Exception);
    return Task.FromResult(0);
}

registered through config.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler()); in my startup configuration, but looking at it after executing Next.Invoke(context) through

context.Get<Exception>(Constants.ContextKeys.Exception);

still doesn't give me all the detail I want, as well as failing to stop at the fault point with the debugger.

Is there a way I can completely turn off all built-in error handling, so that my own middleware can take care of it?

Clarification, since a lot of people seem to misunderstand what I'm after:

  • The built-in error handling in Web API catches some (but not all) exceptions and rewrites them into 500 responses.
  • I want to catch all exceptions, do some logging, and then emit 500 responses with the information I choose (for most of them, see next bullet).
  • There are also some exceptions that signal business logic faults, for which I want to return 40x errors instead.
  • I want this to be at the top of the (app) pipeline, i.e. wrapping everything else in the request lifecycle
  • I want to handle this using OWIN, to make it portable to a possible future self-hosted scenario (i.e. it's not written in stone that this app will always be hosted on IIS - HTTP modules, Global.asax.cs et al are not relevant here).
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
Tomas Aschan
  • 58,548
  • 56
  • 243
  • 402
  • 1
    "the debugger doesn't even stop when the exception is thrown" - Have you tried in VS, Debug->Exception... then select check 'Thrown' for all exceptions. This will cause the debugger to stop each time an exception is thrown. – lcryder Dec 10 '15 at 14:45
  • @lcryder: Yes, but this still doesn't give me all the things I need. For example, it will stop the debugger also on exceptions that I *do* handle somewhere else, and which would thus *never* propagate to the middleware in the first place. – Tomas Aschan Dec 11 '15 at 08:59
  • What exactly are you wanting to do with the errors? Is it purely for logging purposes? if so, then an HTTP module may be appropriate and very simple to achieve. – DavidG Dec 16 '15 at 01:06
  • @DavidG: No, it's both logging and giving an appropriate response. A HTTP module solves the problem in the same way a Middleware does (but with a different pipeline, i.e. not strictly relevant). It is still problematic that Web API has already rewritten the exception into a 500 response, throwing away lots of information, before the middleware (or HTTP module) gets to it. – Tomas Aschan Dec 16 '15 at 08:51

5 Answers5

18

Update: I blogged about this. When researching the blog post, I found some potential for improvement; I've updated the relevant parts of this answer. For more detail on why I think this is better than all other suggestions here, or the default behavior, read the entire post :)


I have now gone with the following approach, which seems to work OK, even if not 100 % compliant with what I was looking for:

  • Create a class PassthroughExceptionHandler:

    public class PassthroughExceptionHandler : IExceptionHandler
    {
        public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
        {
            // don't just throw the exception; that will ruin the stack trace
            var info = ExceptionDispatchInfo.Capture(context.Exception);
            info.Throw();
            return Task.CompletedTask;
        }
    }
    
  • Let that class replace the IExceptionHandler service of Web API:

    config.Services.Replace(typeof(IExceptionHandler), new PassthroughExceptionHandler());
    
  • Create a middleware class which does what I want:

    public class ExceptionHandlerMiddleware
    {
        public override async Task Invoke(IOwinContext context)
        {
            try
            {
                await Next?.Invoke(context);
            }
            catch (Exception ex)
            {
                // handle and/or log
            }
        }
    }
    
  • Register that middleware first in the stack:

    app.Use<ExceptionHandlerMiddleware>()
       .UseStageMarker(PipelineStage.Authenticate)
       // other middlewares omitted for brevity
       .UseStageMarker(PipelineStage.PreHandlerExecute)
       .UseWebApi(config);
    

I will still award the bounty to anyone who comes up with (bounty expired...) I'm still looking for a better solution, which, for example, breaks when an unhandled exception is thrown. (This approach makes VS break when I rethrow the exception in the handler, but the original call stack is lost; I have to set a breakpoint at the faulting line and debug again to be able to intercept the state when an exception is thrown.)

Todd Menier
  • 37,557
  • 17
  • 150
  • 173
Tomas Aschan
  • 58,548
  • 56
  • 243
  • 402
  • 2
    Wow. You had a lot of fun with that! It's amazing! Might be worth a blog post! – Maxime Rouiller Dec 17 '15 at 16:17
  • 1
    Maybe you can use the approach from https://github.com/JustLikeIcarus/aspnetwebstack/blob/master/src/System.Web.Http.WebHost/Routing/HttpRouteExceptionHandler.cs - retrieve ExceptionDispatchInfo and .Throw(), keeping the full call stack. – Test_me Jan 08 '16 at 15:21
  • Note that `await Next?.Invoke(context)` will throw an exception when Next is null. – Piotr Jan 19 '16 at 10:53
  • @Caleb9: Possibly, but I haven't ever been able to create a pipeline where this happens. Even with just `app.Use();` as the entire registration, `Next != null` when I get there on the first request. – Tomas Aschan Jan 19 '16 at 15:19
  • @TomasLycken True it's an edge case, but theoretically someone could call app.Use(null) after registering your middleware. However looking at the [AppBuilder.cs](https://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin/Builder/AppBuilder.cs) it would throw ArgumentNullException in such situation, so no problem :). Still, ReSharper is whining about it. Otherwise good solution and I've used it in the code I'm working on. Thanks. – Piotr Jan 19 '16 at 16:17
  • @Caleb9: You did notice I'm using null propagation from C# 6, right? `Next?.Invoke(context)`, not `Next.Invoke(context)`. R# is not complaining in my end, but our settings may differ :) – Tomas Aschan Jan 20 '16 at 07:18
  • @TomasLycken yes and that's what I'm pointing out: that it does not protect you from the NullReferenceException (and I assume that's what you wanted to achieve here) because if Next would in some way end up being null, then the `?.` operator cuts the call to Invoke, but you're still awaiting null, which is an error and would throw an exception. Try it out with a null object containing an async method :). A completely safe way would be to use a good old `if (Next != null) await Next.Invoke(context);`. At least I think so :S – Piotr Jan 20 '16 at 09:34
  • Have you thought about this: `public class PassthroughExceptionHandler : IExceptionHandler { public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken) { throw new PassThroughException(context.Exception); // pass as inner exception - it'll preserve the stack } }` – Andrey Ilnitsky Apr 04 '17 at 22:01
  • @AndreyIlnitsky: Yes, I have. The inner exception will have a preserved stack trace, but the `PassThroughException` will not, so it will require more work to get the same information when perusing the logs. And the stacktrace from the wrapped exception really doesn't give any information, since everything will go through here. – Tomas Aschan Apr 05 '17 at 09:20
  • Anybody else encountering issue with this configuration whereby CORS headers aren't set in the response? Works fine via non-CORS app (e.g. Paws/Fiddler), but when hitting an API, the underlying exception is thrown and properly handled, but then the response is overwritten by a subsequent dreaded CORS error: "No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access. The response had HTTP status code 500." – Ted Apr 13 '17 at 19:22
  • And yes, CORS is configured properly and works fine everywhere except for when globally handled exceptions are involved (including returning error responses from within a controller). – Ted Apr 13 '17 at 19:22
  • Turns out CORS implementation hoops things for all WebApi sourced exceptions because context.CatchBlock.IsTopLevel is not always set to true because of how the CORS package is implemented. See http://stackoverflow.com/questions/24189315/exceptions-in-asp-net-web-api-custom-exception-handler-never-reach-top-level-whe for a wonderfully thorough discussion with solutions. – Ted Apr 13 '17 at 23:36
  • @Ted: Thanks for checking back with this info! However, I noted that for errors from elsewhere, e.g. in another middleware component, you'll still have to implement CORS yourself - much like the reason to implement this error handling strategy in the first place. – Tomas Aschan Apr 15 '17 at 16:31
  • @TomasLycken do you know if the same can be done with MVC? we have a web site and a web API, would like to share a middleware – chester89 Aug 23 '17 at 10:14
  • @TomasLycken I mean, will it work with custom IExceptionFilter implementation of mine? – chester89 Aug 23 '17 at 10:44
  • @chester89 that probably depends on what versions of the frameworks you're using. Read the docs, or - even better - try! – Tomas Aschan Aug 23 '17 at 16:01
  • Task.CompletedTask is in .Net 4.6+ right? How can i use it in 4.5 one? – Nameless Oct 18 '17 at 08:47
  • @ajadaradhya: Yes, but you can use e.g. Task.FromResult(null). But you should really update - 4.5 isn't supported anymore. – Tomas Aschan Oct 18 '17 at 10:30
1

Not sure if this will work for you, but I have a similar requirement to send all errors back as JSON even for not found errors. I created a base controller and overrode the ExecuteAsync allowing me to create my own responses.

public class ControllerBase : ApiController
{
    protected string ClassName = "ControllerBase::";

    public override System.Threading.Tasks.Task<HttpResponseMessage> ExecuteAsync(System.Web.Http.Controllers.HttpControllerContext controllerContext, System.Threading.CancellationToken cancellationToken)
    {
        try
        {
            System.Threading.Tasks.Task<HttpResponseMessage> TaskList = base.ExecuteAsync(controllerContext, cancellationToken);

            if (TaskList.Exception != null && TaskList.Exception.GetBaseException() != null)
            {
                JSONErrorResponse AsyncError = new JSONErrorResponse();
                AsyncError.ExceptionMessage = TaskList.Exception.GetBaseException().Message;
                AsyncError.ErrorMessage = string.Format("Unknown error {0} ExecuteAsync {1}", ClassName ,controllerContext.Request.RequestUri.AbsolutePath);
                AsyncError.HttpErrorCode = HttpStatusCode.BadRequest;

                HttpResponseMessage ErrorResponse = controllerContext.Request.CreateResponse(AsyncError.HttpErrorCode, AsyncError);

                return System.Threading.Tasks.Task.Run<HttpResponseMessage>(() => ErrorResponse);
            }
            return TaskList;
        }
        catch (Exception Error)
        {
            JSONErrorResponse BadParameters = new JSONErrorResponse();
            BadParameters.ExceptionMessage = Error.Message;
            BadParameters.ErrorMessage = string.Format("Method [{0}], or URL [{1}] not found, verify your request", controllerContext.Request.Method.Method, controllerContext.Request.RequestUri.AbsolutePath);
            BadParameters.HttpErrorCode = HttpStatusCode.NotFound;
            HttpResponseMessage ErrorResponse = controllerContext.Request.CreateResponse(BadParameters.HttpErrorCode, BadParameters);

            return System.Threading.Tasks.Task.Run<HttpResponseMessage>(() => ErrorResponse);
        }
    }
}

public class JSONErrorResponse
{
    //Possible message from exception
    public string ExceptionMessage { get; set; }
    //Possible custom error message
    public string ErrorMessage { get; set; }
    //Http error code
    public HttpStatusCode HttpErrorCode { get; set; }
}
Brian from state farm
  • 2,825
  • 12
  • 17
  • This still won't let me catch errors that happen before the controller is instantiated, or even outside of the actual execution of the controller action. It's also opt-in, since I will have to make sure that all controllers actually inherit this base class; forget that once, and error handling is out the window... – Tomas Aschan Dec 15 '15 at 08:37
  • Have you tried implementing the Global.asax.cs `Application_Error` call? I know of it but have not used it so I do not know if everything goes through there or not. Take a look at this [application error page](https://msdn.microsoft.com/en-us/library/24395wz3.aspx) – Brian from state farm Dec 15 '15 at 13:47
  • We're not using that at all; we're hosting this with the OWIN pipeline (Global.asax.cs isn't relevant in this context). – Tomas Aschan Dec 15 '15 at 16:02
1

You can also try to create your own controller activator, have your custom exception handler and try to use ExceptionFilterAttribute.

  1. Create your controller activator

    public class ExceptionHandlingControllerActivator : IHttpControllerActivator
    {
        private readonly IHttpControllerActivator _concreteActivator;
    
        public ExceptionHandlingControllerActivator(IHttpControllerActivator concreteActivator)
        {
            _concreteActivator = concreteActivator;
        }
    
        public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
        {
            try
            {
                return _concreteActivator.Create(request, controllerDescriptor, controllerType);
            }
            catch (Exception ex)
            {
                // do stuff with the exception
                throw new HttpResponseException(request.CreateResponse(HttpStatusCode.InternalServerError, new ResponseModel(ex)));
            }
        }
    }
    
  2. Create ExceptionFilterAttribute

    public class ExceptionHandlingFilter : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            // do stuff with the exception
    
            var request = context.Request;
            ResponseModel respMod = null;
    
            // Example: if debug constant is not defined, mask exception, otherwise create normal object with message, inner exception and stacktrace
    
            #if !DEBUG
            respMod = new ResponseModel(context.Exception, context.Exception.Message, true);
            #else
            respMod = new ResponseModel(context.Exception);
            #endif
    
            context.Response = request.CreateResponse(HttpStatusCode.InternalServerError, respMod);
        }
    }
    
  3. ResponseModel is a class i do serialize to JSON using Formatters and return by all controller responses, so client is able to identify error data as well as successful response in addition to the HTTP status code.

    config.Formatters.Clear(); // do not need any other
    config.Formatters.Add(new JsonMediaTypeFormatter());
    
  4. Wire up

    // ... [cut] ...            
    config.Filters.Add(new ExceptionHandlingFilter());
    // ... [cut] ...
    config.Services.Replace(typeof(IHttpControllerActivator),
        new ExceptionHandlingControllerActivator(config.Services.GetHttpControllerActivator())
    );
    // ... [cut] ...
    app.UseWebApi(config);
    
0

OWIN is not supposed to handle exceptions like that because web api has its own error handling built in. OWIN is designed to be separate from the application. If you set a breakpoint on the HandleAsync method of your exception handler, you should be able to inspect the context variable and see the details of the exception.

If you are trying to do this just for debugging purposes, setting your breakpoint there should allow you to see the exception. If you need to log the exceptions, the exception handler is the best place to do that, in my opinion.

Hope that helps.

MichaelDotKnox
  • 1,312
  • 1
  • 14
  • 17
  • 1
    But the exception handler mechanism in Web API can't catch all types of errors - for example, if an error occurs during construction of a controller, the Web API error handler won't handle that, and I'll get different error behavior from the framework depending on where in the stack something goes wrong. I want a solution which lets me catch *all* errors and handle them in a consistent way. – Tomas Aschan Dec 13 '15 at 09:49
  • Actually, my previous comment is slightly incorrect: the Web API error handler *will* catch exceptions thrown by controller constructors. However, it *won't* handle exceptions in middlewares earlier in the pipeline, so I'll still need a specific exception handling mechanism at the bottom of my pipeline. Since Web API will handle the exception and rewrite the response, never letting the exception propagate to my middleware, I'll still need duplicated logic. – Tomas Aschan Dec 18 '15 at 09:38
0

Here's something that might help:

https://stackoverflow.com/a/21382651/1419853

http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-21#global-error

Essentially, there is some built in support to catch, handle, and change errors.

It looks something like this:

public class ExceptionLogger : System.Web.Http.ExceptionHandling.ExceptionLogger
{
    Logger _logger;

    public ExceptionLogger(Logger logger)
    {
        _logger = logger;
    }

    public override void Log(ExceptionLoggerContext context)
    {
        _logger.Error(context.ExceptionContext.Exception.ToString());
    }
}
Community
  • 1
  • 1
hatcyl
  • 2,190
  • 2
  • 21
  • 24
  • You'll note from the question that what I already do (and am not so happy about) is very similar; I inject a new `IExceptionHandler` instead of an `IExceptionLogger`, but otherwise I'm using the exact same mechanisms. They are not sufficient to solve my problem. – Tomas Aschan Dec 16 '15 at 09:52
  • IExceptionLogger has more opportunities to "see" exceptions, but lack the facilities to participate in handling them. – G. Stoynev Oct 26 '18 at 01:59