Exploring Web API Exception Handling

In this article, exception handling is explored for Web API. The standard features from Web API 2 are shown as well as the global exception handling released in version 2.1.

code: https://github.com/damienbod/WebApi2_1ExceptionHandling

Using the HttpResponseException class

The following two examples demonstrates how the HttpResponseException class can be used in simple action methods. The HttpStatusCode enum can be used to set the response code.

The first example returns a 301 (Paged moved exception) when the url ../exception/Moved is called.

[RoutePrefix("exception")]
public class ValuesController : ApiController
{

 [HttpGet]
 [Route("Moved")]
 public IEnumerable<string> GetBasicException()
 {
   throw new HttpResponseException(HttpStatusCode.Moved);
   return new string[] { "value1", "value2" };
 }
}

As the results show underneath, the HTTP status code can be set as required.
webapiEx01

This second example returns a bad request example if the id is larger than 3 with a HttpResponseMessage class as the HttpResponseException parameter.

../exception/bad/34

[Route("bad/{id}")]
[HttpGet]
public int Get(int id)
{
 if (id > 3)
 {
   var resp = new HttpResponseMessage(HttpStatusCode.BadRequest)
   {
     Content = new StringContent(string.Format("id > 3, your value: {0}", id)),
     ReasonPhrase = "Your id is too big"
   };
   throw new HttpResponseException(resp);
 }
 return id;
}

The results show that the message in the HttpResponseMessage is returned to the http client.

webapiEx02

Using Exception Filters

Exception Filters can be used to handle unhandled exceptions per controller. These are registered in the filter provider in the Web API config.

using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Filters;

namespace WebApi2_1ExceptionHandling.Attributes
{
    public class ValidationExceptionFilterAttribute : ExceptionFilterAttribute 
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            if (context.Exception is ValidationException)
            {
                var resp = new HttpResponseMessage(HttpStatusCode.BadRequest)
                {
                    Content = new StringContent(context.Exception.Message),
                    ReasonPhrase = "ValidationException"
                };
                throw new HttpResponseException(resp);

            }
        }
    }
}

The WebApiConfig class in the App_Start folder is used to add the filter.

config.Filters.Add(new ValidationExceptionFilterAttribute());

And the filter can then be used in a controller.

[Route("filter/{id}")]
[HttpGet]
[ValidationExceptionFilter]
public int GetWithFilterValidation(int id)
{
  if (id > 3)
  {
     throw new ValidationException(string.Format("Your id is too big, your value: {0}", id));
  }
            
  return id;
}

The action method contains no exception logic, this is all in the exception filter attribute class. The action method has just an attribute. When the URL is called from the client, the ValidationException is thrown in the action method. This is then handled in the ExceptionFilter and returned as a Bad Request.

webapiEx03

NOTE: Even though the ValidationExceptionFilterAttribute class is defined on a separate action method, it is used for all action methods defined in the controller class. The following action method example will also result in the filter logic being executed, even though it has no attribute defined directly on the method or on the controller class.

 [Route("unhandledValidation/{id}")]
 [HttpGet]
 public int GetWithGlobalValidation(int id)
 {
   if (id > 3)
   {
     throw new ValidationException(string.Format("Your id is too big, your value: {0}", id));
   }

   return id;
 }

Logging unhandled exceptions

The ExceptionLogger class which inherits from the IExceptionLogger interface can be used to log all unhandled exceptions. If an unhandled exception occurs, the Log method will be called directly after the exception and before all ExceptionFilter attributes defined for the controller.

using System.Web.Http.ExceptionHandling;
using WebApi2_1ExceptionHandling.Log;

namespace WebApi2_1ExceptionHandling
{
    public class SlabLogExceptionLogger : ExceptionLogger
    {
        public override void Log(ExceptionLoggerContext context)
        {
            TestEvents.Log.UnhandledException(
                context.Request.Method.ToString(),  
                context.Request.RequestUri.ToString(), 
                context.Exception.Message);
        }
    }
}

The IExceptionLogger implementations are added to the config.Services in the WebApiConfig class. You can add as many as you require.

using System.Diagnostics.Tracing;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using Microsoft.Practices.EnterpriseLibrary.SemanticLogging;
using Slab.Elasticsearch;
using WebApi2_1ExceptionHandling.Attributes;
using WebApi2_1ExceptionHandling.Log;

namespace WebApi2_1ExceptionHandling
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            ObservableEventListener listener = new ObservableEventListener();
            listener.EnableEvents(TestEvents.Log, EventLevel.LogAlways, Keywords.All);

            listener.LogToConsole();
            listener.LogToElasticsearchSink("Server=localhost;Index=log;Port=9200", "slab", "WebApiEvent");

            config.MapHttpAttributeRoutes();

            config.Filters.Add(new ValidationExceptionFilterAttribute());
            config.Services.Add(typeof(IExceptionLogger),  new SlabLogExceptionLogger());
            config.Services.Replace(typeof (IExceptionHandler), new GlobalExceptionHandler());

        }
    }
}

In this example, the exception is logged using semantic.logging to a elasticsearch persistence.

Global IExceptionHandler

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.

using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Cache;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using System.Web.Http.Results;

namespace WebApi2_1ExceptionHandling
{
    public class GlobalExceptionHandler : ExceptionHandler
    {
        public override void Handle(ExceptionHandlerContext context)
        {
            if (context.Exception is ValidationException)
            {
                var resp = new HttpResponseMessage(HttpStatusCode.BadRequest)
                {
                    Content = new StringContent(context.Exception.Message),
                    ReasonPhrase = "ValidationException"
                };

                context.Result = new ErrorMessageResult(context.Request, resp);
            }
            else
            {
                // Do something here...
            }
        }

        public class ErrorMessageResult : IHttpActionResult
        {
            private HttpRequestMessage _request;
            private HttpResponseMessage _httpResponseMessage;


            public ErrorMessageResult(HttpRequestMessage request, HttpResponseMessage httpResponseMessage)
            {
                _request = request;
                _httpResponseMessage = httpResponseMessage;
            }

            public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
            {
                return Task.FromResult(_httpResponseMessage);
            }
        }
    }
}

In the Web API config, the IExceptionHandler has to be replaced unlike the IExceptionLogger. Only 1 IExceptionHandler can be used for the service. This IExceptionHandler will only be called if the service can still define a response. IExceptionLogger will always be called.

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

IoC with IExceptionHandler, IExceptionLogger, IFilterProvider

When adding an IoC for these elements, you need to add the container in the constructor of the IExceptionLogger, IExceptionHandler or IFilterProvider. See https://damienbod.wordpress.com/2014/01/04/web-api-2-using-actionfilterattribute-overrideactionfiltersattribute-and-ioc-injection/ for details on how to do this.

HTTP status codes for REST
brockallen has created a great post showing when to use which code.

HttpError class
This class can be used to return error information in a serializable container. It is just a key/pair collection. This class can be used in the Request.CreateResponse method which returns a HttpResponseMessage.

Here’s an example:

        [Route("HttpError/{id}")]
        [HttpGet]
        public HttpResponseMessage GetProduct(int id)
        {
            if (id > 3)
            {
                var message = string.Format("id > 3 {0} ", id);
                HttpError err = new HttpError(message);
                err["error_id_Validation"] = 300;
                return Request.CreateResponse(HttpStatusCode.BadRequest, err);
            }
            else
            {
                return Request.CreateResponse(HttpStatusCode.OK, id);
            }
        }

The ApiContoller class also includes some methods for returning common HTTP status codes, for example InternalServerError() or InternalServerError(Exception). These methods can be used instead of HttpResponseException.

Links:

http://www.asp.net/web-api/overview/web-api-routing-and-actions/exception-handling

http://aspnetwebstack.codeplex.com/wikipage?title=Global%20Error%20Handling&referringTitle=Specs

http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

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

http://blogs.msdn.com/b/amyd/archive/2014/02/06/response-400-bad-request-on-long-url.aspx?CommentPosted=true#commentmessage

http://stackoverflow.com/questions/10655350/returning-http-status-code-from-asp-net-mvc-4-web-api-controller

http://stephenwalther.com/archive/2012/03/05/introduction-to-the-asp-net-web-api

http://www.prideparrot.com/blog/archive/2012/3/creating_a_rest_service_using_asp_net_web_api

http://soabubblog.wordpress.com/2013/07/07/web-api-httpresponsemessage/

14 comments

  1. […] Damien (damienbod) has a nice overview of Web API Exception handling, complete with a lot of references: Exploring Web API Exception Handling […]

  2. Hi damien,

    You said “NOTE: Even though the ValidationExceptionFilterAttribute class is defined on a separate action method, it is used for all action methods defined in the controller class”.
    It happens because you set your attribute as a global filter.

    Remove the next line
    config.Filters.Add(new ValidationExceptionFilterAttribute());
    and attribute will be called only for the action you want to target.

    Regards,
    Phil

    1. Hi Phil

      Thanks for this info.

      greetings Damien

  3. My have missed it. Any thoughts on handling an exception that, as an example, was thrown in the constructor of the controller?

    1. Hi Carl

      If you throw an Exception in the constructor of a controller, you can handle this in a ExceptionHandler class,ie the global exception handler as required.

      Greetings Damien

  4. Why the Handle method from GlobalExceptionHandler is throwing a new Exception instead of setting the context.Result?

    1. It’s not really an exception, it’s a Bad Request response which sets to correct HTTP status code.

  5. Greg · · Reply

    I used your GlobalExceptionHandler example in my work. However, in my experience, when the HttpResponseException is thrown within the GlobalExceptionHandler class, the server returns the standard YSOD with a 500 error, regardless of what I put within the HttpResponseException.

    In diagnosing this, I came across someone having a similar problem with the 500 error and YSOD on HttpResponseException (though under different circumstances), and their problem had to do with their controller erroneously not inheriting from ApiController.

    Though, I was inheriting from ApiController. So as a test, I constructed a controller method that simply threw an arbitrary HttpResponseException when called. This worked as intended. This leads me to believe that expected handling of HttpResponseExceptions is intrinsically tied to ApiController objects (hence why attribute filters applied to and/or within a controller object, the way most examples online take care of the exception filtering, seem to work), so there might be a problem with your GlobalExceptionHandler example.

    Thoughts?

    1. Hi Greg

      thanks for this. Your dead right, I don’t know how I missed this. I corrected the example so that it works now.

      The HttpResponseException can only be used inside a controller or filter.

      I changed the example to set the context.Result with a custom ErrorMessageResult : IHttpActionResult. This can then be used with any HttpResponseMessage

      thanks Damien

  6. Could you clarify if ExceptionLogger and ExceptionHandler get called for ALL unhandled exceptions. ?
    For example if the code throws:
    1. HttpResponseException or
    2. Exception is thrown by the framework during serialization of response content or
    3. Exception thrown during Routing or
    4. Exception thrown inside Message Handlers or
    5. Any other unhandled exception

  7. Johnb925 · · Reply

    Thanks so much for the article.Much thanks again. Great. deddfafebadc

  8. “If an exception occurs, the IExceptionLogger will be called first, then the controller ExceptionFilters and if still unhandled, the IExceptionHandler implementation.”

    How is the exception determined to have been handled? In MVC, there is a read/write property on ActionExecutedContext:

    public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
    {
    public override void OnException(ExceptionContext context)
    {
    if (!context.ExceptionHandled)
    {
    // Handle the exception

    // Mark exception handled
    context.ExceptionHandled = true;
    }
    }
    }

  9. Bouazza · · Reply

    Is there match in asp.net 5? thank you

  10. […] Exploring Web API Exception Handling […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: