#Exception Handling
##HttpResponseException
What happens if a Web API controller throws an uncaught exception? By default, most exceptions are translated into an HTTP response with status code 500, Internal Server Error.
The HttpResponseException type is a special case. This exception returns any HTTP status code that you specify in the exception constructor. For example, the following method returns 404, Not Found, if the id parameter is not valid.
[HttpGet]
[Route("{id}")]
public LanguageViewModel Get(int id)
{
LanguageViewModel model = _service.GetLanguageById(id);
if (model == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return model;
}
For more control over the response, you can also construct the entire response message and include it with the HttpResponseException:
[HttpGet]
[Route("{id}")]
public LanguageViewModel Get(int id)
{
LanguageViewModel model = _service.GetLanguageById(id);
if (model == null)
{
HttpError error = new HttpError();
error.Add("IdDoesNotExistException", Resources.Resources.IdDoesNotExistException + " Id: " + id);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.NotFound,error)
throw new HttpResponseException(response);
}
return model;
}
##Exception Filters
Like described earlier in this document you can customize how Web API handles exceptions by writing an exception filter. An exception filter is executed when a controller method throws any unhandled exception that is not an HttpResponseException exception. The HttpResponseException type is a special case, because it is designed specifically for returning an HTTP response.
The following is the same example:
using CoursesAPI.Services.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Filters;
using System.Web.Http.ModelBinding;
namespace CoursesAPI.Filters
{
public class AppExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
base.OnException(actionExecutedContext);
if (actionExecutedContext.Exception is CoursesAPIValidationException)
{
var ex = actionExecutedContext.Exception as CoursesAPIValidationException;
HttpError error = new HttpError();
var validationResults = ex.GetValidationResults();
if (validationResults.Count > 0 && validationResults.Where(x => x.ErrorMessage == ErrorCodes.IdDoesNotExistException).FirstOrDefault() != null)
{
error.Add("CoursesAPIValidationException", Resources.Resources.IdDoesNotExistException);
}
else
{
ModelStateDictionary modelState = actionExecutedContext.ActionContext.ModelState;
error = GetErrors(modelState, true);
}
actionExecutedContext.Response = actionExecutedContext.ActionContext.Request.CreateErrorResponse(HttpStatusCode.PreconditionFailed, error);
}
}
...
...
}
}
The Response property of the HttpActionExecutedContext object contains the HTTP response message that will be sent to the client.
There are several ways to register a Web API exception filter:
- By action
- By controller
- Globally
To apply the filter to a specific action, add the filter as an attribute to the action:
[AppExceptionFilter]
public LanguageViewModel Get(int id)
{
...
}
To apply the filter to all of the actions on a controller, add the filter as an attribute to the controller class:
[AppExceptionFilter]
public class LanguageController : ApiController
{
...
}
To apply the filter globally to all Web API controllers
config.Filters.Add(new AppExceptionFilter());
This allows the methods or other layers to contain no exception logic, this is all in the exception filter attribute class.
##Logging unhandled exceptions
Today there's no easy way in Web API to log or handle errors globally. Some unhandled exceptions can be processed via exception filters, but there are a number of cases that exception filters can't handle. For example:
- Exceptions thrown from controller constructors.
- Exceptions thrown from message handlers.
- Exceptions thrown during routing.
- Exceptions thrown during response content serialization.
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.
Exception loggers provide you a single point into which you can plug in a service that would log information about any exceptions occurring in the Web API pipeline – regardless where they originated from. Moreover, the relevant ExceptionContext will be provided to you inside that logger, giving you access to contextual information which are specific for the controller or filter or any other Web API component from which the exception was thrown.
The great thing about exception loggers is that they will always be called – even if the exception occurs in funky edge situation such as when writing to the response stream from the media type formatter. Finally, an important point is that you can register multiple ExceptionLoggers in Web API, so you can pipe this exception information into different targets.
The following is a simple example of a logger.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http.ExceptionHandling;
namespace CoursesAPI.Loggers
{
public class TraceExceptionLogger : ExceptionLogger
{
public override void Log(ExceptionLoggerContext context)
{
System.Diagnostics.Trace.TraceError(context.ExceptionContext.Exception.ToString());
}
}
}
The implementation above is very simple but if you wanted to use an external service like for example Raygun you could do the following:
using Mindscape.Raygun4Net;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http.ExceptionHandling;
namespace CoursesAPI.Loggers
{
public class RaygunExceptionLogger : ExceptionLogger
{
public override void Log(ExceptionLoggerContext context)
{
//Create instance of Logging API and log the exception with API
RaygunClient _client = new RaygunClient("W6mFxHVXRvLBIyXEPvjiGA==");
_client.Send(context.Exception);
}
}
}
The IExceptionLogger implementations are added to the config.Services in the WebApiConfig class. Again, You can add as many as you require.
using CoursesAPI.Filters;
using CoursesAPI.Loggers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
namespace CoursesAPI
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
// Add a filter for exceptions
config.Filters.Add(new AppExceptionFilter());
// Add logger
config.Services.Add(typeof(IExceptionLogger), new TraceExceptionLogger());
config.Services.Add(typeof (IExceptionLogger), new RaygunExceptionLogger());
// add handler for exceptions
config.Services.Replace(typeof(IExceptionHandler), new CourseAPIExceptionHandler());
// Set Language handler to detect and set localized languages
config.MessageHandlers.Add(new LanguageMessageHandler());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
##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.
Here is an example:
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
namespace CoursesAPI.Loggers
{
public class CourseAPIExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new TextPlainErrorResult
{
Request = context.ExceptionContext.Request,
Content = "Oops! Sorry! Something went wrong." +
"Please contact [email protected] so we can try to fix it."
};
}
private class TextPlainErrorResult : IHttpActionResult
{
public HttpRequestMessage Request { get; set; }
public string Content { get; set; }
public HttpError httpError { 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);
}
}
}
}
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.
Now that we have seen all the possible implementations for error handling the following points describe when to use which:
- Exception loggers are the solution to seeing all unhandled exception caught by Web API.
- Exception handlers are the solution for customizing all possible responses to unhandled exceptions caught by Web API.
- Exception filters are the easiest solution for processing the subset of unhandled exceptions related to a specific action or controller.
Related links and more material