34

Closed

Global error handler for Web API

description

If my Web API application is going to return an error to a client, I want to know about it.

I want to set up something like ELMAH to notify me when the problem occurs, and I want full details of the error, including exception stack trace, logged on the server.

In MVC, the Application_Error event is how you achive this, but in Web Api this event often isn't triggered because exceptions are converted into HttpResponseMessages higher in the call stack, and because some error conditions create a HttpResponseMessage directly without using an exception. One source of this is HttpControllerDispatcher.SendAsync, which catches all exceptions and turns them into error responses, discarding the exception information.

Here are some examples of exceptions that I am interested in handling:
  • Exceptions thrown from action methods and action filters
  • Exceptions thrown when creating a controller instance
  • Exceptions due to routing errors, bad requests, authentication problems, and invalid OData queries
  • Exceptions thrown when an IQueryable returned by an action method fails to execute
  • Exceptions thrown by custom message handlers and formatters
When I started using Web API, I was surprised when my Application_Error handler did not pick up an action method exception. I added a global ExceptionFilterAttribute to take care of that, but then found several other categories of errors that could be returned to the client and still leave silence in the server logs.

In a production environment, relying on clients to report errors and having no error information available on the server will make diagnosing problems reactive and difficult.

Please provide a global error handling event for Web API, so that error details can be logged and notifications sent. The event should provide access to unhandled exceptions and non-success HttpResponseExceptions and HttpResponseMessages. In the case of HttpError objects created from an exception, the exception details should be retained for the error handling event.
Closed Oct 30, 2013 at 9:18 PM by kichalla
Verified.

comments

yishaigalatzer wrote Apr 26, 2013 at 12:30 AM

Thanks for your report.

We are going to look into it, and get back here with next steps.

In the meantime I wanted to suggest looking into, a tracing package we released not too long ago.
http://www.nuget.org/packages/microsoft.aspnet.webapi.tracing

There is also an Elmah package that was updated not too long ago, but I have no context on the quality or if it answers the issue you raise above.

http://nuget.org/packages/Elmah.Contrib.WebApi/

Here are a couple of blog post by Ron Cain (a bit old but still very relevant) on the subject of tracing
http://blogs.msdn.com/b/roncain/archive/2012/04/12/tracing-in-asp-net-web-api.aspx
http://blogs.msdn.com/b/roncain/archive/2012/08/16/asp-net-web-api-tracing-preview.aspx

yishaigalatzer wrote May 1, 2013 at 8:43 PM

** Closed by yishaigalatzer 05/01/2013 1:43PM

NikhilK wrote May 8, 2013 at 6:40 PM

Per our mail thread, it would be good to revisit this...

Specifically it would be useful to have a well-known extensibility point that allows one to plug in an exception handler that enables two things:
  1. Capturing the error, whether it is for logging or any other reason.
  2. Converting the error to an HttpError instance, so any custom logic in error creation, inclusion of error detail etc. can be executed.

kristan2345 wrote May 22, 2013 at 7:40 PM

I would like to be able to have both IIS custom errors for normal http exceptions AND json formatted exceptions when they emanate from the Web API.

At the moment, I have to always return HTTP 200 from the Web API so that the JSON formatted response does not get hijacked by IIS (e.g., returning a 500 http status code will mean IIS will override my JSON output with a custom html message, which is what I want for all other scenarios other than WEB API interactions).

Also, the problem is, sometimes there's a deserialization exception that occurs further up the stack from my actual Web API code (usually the deferred invocation of a LINQ query)... I cannot intercept this exception and return a HTTP 200 manually to avoid the IIS override issue, so in this case Web API will return a 500 which subsequently gets overridden by IIS.

I have tried using a DelegatingHandler to intercept the Web API request/response cycle so that I could at least log the error, even when IIS overrides the JSON response; but it seems even at that level, no exception has occurred from the deferred LINQ query.

If I'm wrong, please could you demonstrate how to work-around this. A lot of people seem to be talking about this, but with no real solutions forthcoming.

Many thanks for your help
Kris

jdaley wrote Jun 9, 2013 at 7:14 AM

For those looking for workarounds until this gets implemented, I just found "Exception Handling for Web API Controller Constructors" to deal with the second bullet point in this issue.

trisys wrote Jun 11, 2013 at 11:45 AM

I agree - it is hard enough as it is doing web API stuff - any help most appreciated.

eilonlipton wrote Jun 17, 2013 at 12:37 AM

Youssef, if you have a chance to take a look at this that would be great. Even if there's no straightforward fix we would like to work with UE on this and produce guidance on how to handle errors in a Web API service, including all the various places where errors can happen. If we end up just producing guidance, that's OK - we can take this as a vNext feature for any actual implementation work that we'd want to consider.

MarkWH wrote Jun 25, 2013 at 6:17 AM

This definitely is a short coming in webapi. I need to be able to handle exceptions thrown by the IQueryable or Media formatters in a way that I can both log the failure and send a coherent response back to the client.

IsaacAlexander wrote Jul 17, 2013 at 9:52 PM

This feature is essential to exception transformation and filtering.

JohnSolo wrote Aug 3, 2013 at 12:57 PM

Hi, this is, IMHO, kind of a major oversight. Is there any news on this?

Kings wrote Sep 5, 2013 at 1:25 PM

Full error handling should be a must for any professional framework.

Hope this issue would be solved soon.

yishaigalatzer wrote Sep 5, 2013 at 3:23 PM

@Kings, @JohnSolo. Can you please share the examples of where you are missing error handling support? This issue is very general and we do intend to address it. At the same time, there is already support in the framework for many cases, and your case might be already covered. We might be able to provide input on how to deal with your specific issues.

yishaigalatzer wrote Sep 5, 2013 at 3:32 PM

@MarkWH (and all others commenting or interested in the issue)

It would help if you can share a simple example of the cases you want handled. There are few things we will be able to handle and few that not so easily.

For example if your formatter throws during the time where it writes the body out, you are out of luck since the response and headers already went on the wire. Yes you can probably log, but you will have a hard time writing a coherent response to the client.

By sharing your specific example(s) we will both be able to plan fixing this bug better and attempt to address your specific issues as well as provide current how to guidance.

Kings wrote Sep 10, 2013 at 2:34 PM

@yishaigalatzer I need a place to catch all exceptions thrown, as Application_Error was. I need to trace and also to manage some exception's information. I think full exception handling is a common feature needed by many developers.

In ASP.NET MVC 4 there is not a way to catch any unhandled exception, and I don't like to fill all my code with try/catch blocks.

Is there any way to solve this issue?

Thanks.

yishaigalatzer wrote Sep 10, 2013 at 5:55 PM

@Kings

We are looking at this issue next, and we will circle back when we have a design. We plan to post the design document on this site as soon as it's ready to review.

Just to be clear, this issue is for Web API and not MVC4. I'm assuming that's what you meant.

Thanks,
Yishai

jdaley wrote Sep 11, 2013 at 10:07 AM

I agree with @Kings that it should catch all exceptions.

Errors that I anticipate and want to handle in a specific way should still have their handling code at the appropriate level. If the error can occur in several controllers, I'll consider an exception filter. If it can occur earlier in the pipeline, I might use a custom handler. If it can occur in just one action method, I'll use a try/catch block in that method.

The global error handler is a generic handler for all errors, including ones I didn't anticipate. The main use case for it is logging and notifying an administrator. I might also want to override the response to the client here, but if I'm doing that at this point in the pipeline, it's going to be pretty generic and not depend much on exactly what type of error has occurred.

As an example, let's say I'm writing some fresh new REST services that expose data from a legacy system. I've written a couple of controllers with action methods that pull data from the legacy system and return it. One of the errors I anticipate is the legacy system being down for maintenance. When this occurs, I don't want to return the raw network exceptions back. I want to return a nice "Legacy system X is down, try again later" message. Also, I want full details of the error logged on my server, because I want full details of all errors logged on my server.

To implement this, ideally I'll do two things:
  • create an exception filter that catches the network errors from the legacy system and wraps them into error responses with user-friendly messages, and apply this exception filter to each relevant controller
  • implement my global error handler to log all errors that are returned from my server
The global error handler is not the place to do specific error handling. If my global error handler ends up as a giant if statement testing for different error types and handling them appropriately, I'm doing it wrong. Likewise, generic application-wide logging and error notification code does not belong copy-pasted into half a dozen different framework extensibility points, with the blind hope that I've covered all the bases.

As far as design of this goes, I'm imagining the global error handler will provide access to all non-success responses just before they get sent out. The only thing available at this point will be the HttpResponseMessage, so changes are needed to retain error information up to this point. For example, CreateErrorResponse immediately discards exception information if ShouldIncludeErrorDetail is false. It needs to hold on to that information while the rest of the pipeline executes, and only discard it right at the end when the response is formatted. I don't know enough about Web API internals to know if this particular design is practical, and Yishai's point about headers getting sent before the formatter is done certainly throws a spanner in the works. I'm looking forward to seeing the design doc you guys post.

JohnSolo wrote Sep 11, 2013 at 1:56 PM

@yishaigalatzer as Kings has said, I am looking for a way to capture and log any unhandled exceptions that occur anywhere in my API. I am new to Web API (ASP.Net and IIS in general actually) so it is entirely possible that I am overlooking some feature or configuration option. I can tell you what I have done thus far to facilitate global exception handling.

First I created an ExceptionMessageHandler delegating handler and made it the first handler in the pipeline and used this code in the handler:
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
    {
        if (task.IsFaulted)
        {
            return this._ErrorHandler.HandleException(request, task.Exception);
        }
        else
        {
            return task.Result;
        }
    });
Very quickly I learned that any unhandled exceptions in my controller (possibly elsewhere) are handled automatically by the framework which returns a HTTP response with status code 500. My first thought was, yuck. Can I disable this behavior somehow? In any event I modified my method like this to get the error from the HTTP response, log it and return my own HTTP response:
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
    {
        if (task.IsFaulted)
        {
            return this._ErrorHandler.HandleException(request, task.Exception);
        }
        else if (task.Result.StatusCode == HttpStatusCode.InternalServerError)
        {
            return this._ErrorHandler.HandleResponse(task.Result);
        }
        else
        {
            return task.Result;
        }
    });
I then learned that this will not handle exceptions in the message handler pipeline and modified it yet again to this:
try
{
    return base.SendAsync(request, cancellationToken).ContinueWith(task =>
        {
            if (task.IsFaulted)
            {
                return this._ErrorHandler.HandleException(request, task.Exception);
            }
            else if (task.Result.StatusCode == HttpStatusCode.InternalServerError)
            {
                return this._ErrorHandler.HandleResponse(task.Result);
            }
            else
            {
                return task.Result;
            }
        });
    
}
catch (Exception e)
{
    TaskCompletionSource<HttpResponseMessage> tsc = new TaskCompletionSource<HttpResponseMessage>();
    tsc.SetResult(this._ErrorHandler.HandleException(request, e)); 
    return tsc.Task;
}
It seems as though this is still insufficient. Occasionally something sneaks by my exception message handler and I have not figured out how. Any comments, criticism or advice is welcome.

yishaigalatzer wrote Sep 17, 2013 at 10:16 PM

@David - Please look at this bug as well when considering fixes for this bug:
https://aspnetwebstack.codeplex.com/workitem/442/edit

danroth27 wrote Sep 17, 2013 at 11:50 PM

danroth27 wrote Sep 17, 2013 at 11:51 PM

michaelsync wrote Sep 19, 2013 at 5:38 AM

Why can't you just use elmah? You can configure elmah to send the mail if the exception occurs.

tugberk wrote Sep 19, 2013 at 8:48 AM

@michaelsync

You can only use Elmah when you are hosting on ASP.NET. It's tightly coupled to HttpContext.

michaelsync wrote Sep 21, 2013 at 7:27 PM

@tugberk

Yes. I am aware of it. Web API didn't use HttpContext?

tugberk wrote Sep 22, 2013 at 6:33 AM

@michaelsync

An ASP.NET Web API application can be hosted on top of ASP.NET but it doesn't have to be. You can self host it on top of anything. You can even host it in-memory for integration testing purposes.

davidmatson wrote Oct 28, 2013 at 11:03 PM

commit 1b17f6474d3588e9bd1ddb856ffad78ac9537a98
commit 9d1be9f479dd9c802a5be459102b8c0e67328eb8
commit b8bfe66e3f4a53d7c123d0e90538e8f508756f37

Kings wrote Nov 6, 2013 at 2:11 PM

Well,

Testing nightly build (5 november 2013) and adding ValidateModelAttribute in this way:
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.Filters.Add(new ValidateModelAttribute());

            config.Services.Replace(typeof(IExceptionHandler), new CustomExceptionHandler());
            config.Services.Replace(typeof(IExceptionLogger), new TraceExceptionLogger());
    
            config.Routes.MapHttpRoute(
                name: "DetaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            ); 
        }
    }
When a controller method has an invalid model, Exception logger is called, but not Exception handler, so a pre-defined validation message, and not a custom one, is received by clients.

kichalla wrote Nov 6, 2013 at 4:16 PM

What kind of filter is ValidateModelAttribute? (action filter I suppose?)...also how are you implementing CustomExceptionHandler? (by implementing the interface IExceptionHandler or by deriving from ExceptionHandler?)...

kichalla wrote Nov 6, 2013 at 5:01 PM

I am unable to repro your scenario. I am seeing both exception logger and handler being called. Would it be possible for you to share a repro to my email at: kiranchalla@hotmail.com

Kings wrote Nov 7, 2013 at 11:07 AM

Sorry, it's my fault.

My CustomExceptionHandler class inherits from ExceptionHandler, and my ValidateModelAttribute filter is defined as follows:
    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            // Validate model (custom validation)
            // ...

            // Check if modelstate is valid
            if (!actionContext.ModelState.IsValid)
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }
My problem here is how to handle internal ModelState exceptions messages, where model property types are validated automatically (not by custom validation process), in order to localize default model validation messages like:

{"Message":"The request is invalid.","ModelState":{"product.ProductId":["Error converting value {null} to type 'System.Int32'. Path 'ProductId', line 1, position 44."],"product.Price":["Error converting value \"\" to type 'System.Double'. Path 'Price', line 1, position 103."],"product.Discount":["Error converting value \"\" to type 'System.Double'. Path 'Discount', line 1, position 117."]}}

This same problem, regarding handling and localization of default response messages, is reproduced also when an invalid route is called:

_http://localhost/api/customer_invalid_route/3__ _{"Message":"No HTTP resource was found that matches the request URI 'http://localhost/api/customer_invalid_route/3'.","MessageDetail":"No type was found that matches the controller named 'perfil'."}

Or when an Authorization is required:

__http://localhost/api/customers/3__ _{"Message":"Authorization has been denied for this request."}_

We would like to handle and localize all the messages returned by our web api.

kichalla wrote Nov 7, 2013 at 4:27 PM

Thanks for the details, Kings. Since these are more kind of how-to kind of questions, can you can start a discussion thread over here instead? (https://aspnetwebstack.codeplex.com/discussions/topics/5322/asp-net-web-api) ..this way any other users can also find this discussion useful...

brajkovic wrote Nov 19, 2013 at 10:13 PM

Hey guys,

When can we expect to see this in either a pre-release build that's on NuGet or a full release?

martinlandalv wrote Feb 20 at 8:51 AM

This is supposedly fixed in 5.1 preview and I'm running 5.1.1. How was this fixed?

I'm still unable to catch and log errors that occur due to exceptions in deferred execution when an action returns an IEnumerable or IQuerably (which I use for some OData actions).

I've tried exception filters (per action and global) and replacing the IExceptionFilter and IExceptionLogger service. What am I missing?