Introduction
Whenever there is any unhandled exception in Web API controller class, most of the actual exceptions are indicated as HTTP status code 500, i.e. Internal Server Error. This generic error message is of little use to the client as it doesn’t reveal the actual cause of the server side error. However, Web API allows you to fine tune and customize the HTTP errors that are sent to the client browser. This article discusses how this is done.
Throwing Exceptions from an API Controller
Consider a Web API controller named CustomerController as shown below:
public class CustomerController : ApiController { public Customer Get(string id) { NorthwindEntities db=new NorthwindEntities(); var data = from item in db.Customers where item.CustomerID == id select item; Customer obj = data.SingleOrDefault(); if (obj == null) { throw new Exception("CustomerID Not Found in Database!"); } else { return obj; } } ... }
The CustomerController class uses the Customers table of the Northwind database. The above code shows only the Get() method of the CustomerController class. The Get() method accepts a CustomerID and returns a Customer object matching the specified CustomerID. If there is no Customer entry for the specified CustomerID, an exception is thrown with message “CustomerID Not Found in Database!”.
To invoke the Get() method from the client side you can use the jQuery $.ajax() method as shown below:
$.ajax({ type: "GET", url: '/api/Customer', data: {id:$("#txtCustomerID").val()}, contentType: "application/json; charset=utf-8", dataType: "json", success: function (result) { alert(result.CustomerID + " - " + result.CompanyName); }, error: function (err,type,httpStatus) { alert(err.status + " - " + err.statusText + " - " + httpStatus); } })
Since you are invoking the Get() method, the $.ajax() method uses GET as the request type. The url is specified as /api/Customer. Based on the request type and the URL, the Web API framework will invoke the Get() method of Customer API controller. The Get() method takes one parameter – CustomerID. The CustomerID is grabbed from a textbox (txtCustomerID) in the above example and is wrapped in an object literal with key id. This key name must be the same as the parameter name. If the Get() method completes successfully, a function as specified by the success setting is called. The success function receives the Customer object returned by the Get() method. The success method simply displays the CustomerID and CompanyName property values of the Customer object. The error function is called in case there is any error while invoking the Get() method. The error function receives three parameters, viz. error object, type of error and HTTP status.
If you run the above code and supply some CustomerID that doesn’t exist, you will get an error message as shown below:
500 Error – Internal Server Error
As you can see, though the server side exception message is “CustomerID Not Found in Database!” the client always shows the error message as “Internal Server Error”.
Using HttpResponseException Class to Throw Exceptions
To emit some meaningful error messages to the client you can use the HttpResponseException class. Let’s see how the Get() method can be modified using the HttpResponseException class. Have a look at the code below:
public Customer Get(string id) { NorthwindEntities db=new NorthwindEntities(); var data = from item in db.Customers where item.CustomerID == id select item; Customer obj = data.SingleOrDefault(); if (obj == null) { HttpResponseMessage msg = new HttpResponseMessage(HttpStatusCode.NotFound) { Content = new StringContent(string.Format("No customer with ID = {0}", id)), ReasonPhrase = "CustomerID Not Found in Database!" }; throw new HttpResponseException(msg); } else { return obj; } }
Notice the code marked in bold letters. The code creates an instance of HttpResponseMessage class by specifying HTTP status code as NotFound (i.e. 404). It also sets the Content and ReasonPhrase properties to a custom error message. The Content property specifies the response text whereas the ReasonPhrase property controls the descriptive text accompanying the status code. Finally, an HttpResponseException is thrown by passing the HttpResponseMessage instance that you just created. If you run the modified version of Get() method and supply some nonexistent CustomerID you will get an error message like this:
404 Error
As you can see, this time the client gets the custom error message as specified by the ResponsePhrase property in the code instead of the standard message – “Internal Server Error”. If you observe the HTTP response in Chrome Developer Tools you will see something like this:
HTTP response in Chrome Developer Tool
Writing a Custom Exception Filter to Process Unhandled Exceptions
In the modified version of the Get() method you checked some condition and based on the outcome of the condition HttpResponseException was thrown. However, at times there can be unhandled exceptions in the code. These unhandled exceptions too don’t generate any meaningful error message for the client. To deal with such unhandled exceptions you can create an Exception Filter. An exception filter is a class that implements the IExceptionFilter interface. To create a custom exception filter you can either implement the IExceptionFilter interface yourself or create a class that inherits from the inbuilt ExceptionFilterAttribute class. In the later approach all you need to do is override the OnException() method and plug-in some custom implementation. The following code shows how this is done.
public class MyExceptionFilter:ExceptionFilterAttribute { public override void OnException(HttpActionExecutedContext context) { HttpResponseMessage msg = new HttpResponseMessage(HttpStatusCode.InternalServerError) { Content = new StringContent("An unhandled exception was thrown by Customer Web API controller."), ReasonPhrase = "An unhandled exception was thrown by Customer Web API controller." }; context.Response = msg; } }
As you can see the MyExceptionFilter class inherits from ExceptionFilterAttribute class and overrides the OnException() method. The OnException() method is called whenever there is any unhandled exception. The OnException() method essentially creates a new HttpResponseMessage class as discussed earlier and then sets the Response property of the HttpActionExecutedContext to the newly created HttpResponseMessage object.
Using a Custom Exception Filter
Once you develop a custom exception filter you can put it to use in two ways:
- You can register it in the Application_Start event of Global.asax
- You can decorate the Get() method with the exception filter attribute
To register your custom exception filter using the first technique, add the the following piece of code to the Global.asax file.
public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { GlobalConfiguration.Configuration.Filters.Add(new WebAPIExceptionsDemo.MyExceptionFilter()); AreaRegistration.RegisterAllAreas(); ... } }
Notice the line of code marked in bold letters. The custom exception filter is added to the Filters collection using the GlobalConfiguration class. This way all the methods automatically use the MyExceptionFilter exception filter for dealing with unhandled exceptions.
Alternatively, you can also mark the individual methods with the custom exception filter attribute as shown below:
[MyExceptionFilter] public Customer Get(string id) { ... }
As you can see the Get() method is now decorated with [MyExceptionFilter] attribute. To test the exception filter you can modify the Get() method to throw an unhandled exception.
[MyExceptionFilter] public Customer Get(string id) { ... if (obj == null) { if (id.Length != 5) { throw new Exception("Invalid Customer ID!"); } HttpResponseMessage msg = new HttpResponseMessage(HttpStatusCode.NotFound) { Content = new StringContent(string.Format("No customer with ID = {0}", id)), ReasonPhrase = "CustomerID Not Found in Database!" }; throw new HttpResponseException(msg); } ... }
The following figure shows a sample run of the Get() method when there is an unhandled exception.
A sample run of the Get() method
Summary
By default any unhandled exceptions thrown inside Web API are propagated to the client as “Internal Server Error”. This generic error message is of little use to the client. Using HttpResponseException class you can return meaningful error messages to the client. You can also create an exception filter to deal with unhandled exceptions and to return custom error messages to the client.