Error and Exception handling in ASP.NET Web API


  • HttpResponseException
  • Exception Filters
  • HttpError

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.

This exception class allows us to return HttpResponseMessage to the client. It returns HTTP status code that is specified in the exception Constructor.

For example, the following method of EmployeeController returns Status code 404 (not found), if employee data is not found for ID.

public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
    return item;
}

We have more control over the response because we can pass the entire response message (using HttpResponseMessage) to the Constructor of HttpResponseException.

public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
        {
            Content = new StringContent(string.Format("No product with ID = {0}", id)),
            ReasonPhrase = "Product ID Not Found"
        };
        throw new HttpResponseException(resp);
    }
    return item;
}

HttpError

The HttpError object provides a consistent way to return error information in the response body. The following example shows how to return HTTP status code 404 (Not Found) with an HttpError in the response body.C#Copy

public HttpResponseMessage GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        var message = string.Format("Product with id = {0} not found", id);
        return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
    }
    else
    {
        return Request.CreateResponse(HttpStatusCode.OK, item);
    }
}

CreateErrorResponse is an extension method defined in the System.Net.Http.HttpRequestMessageExtensions class. Internally, CreateErrorResponse creates an HttpError instance and then creates an HttpResponseMessage that contains the HttpError.

In this example, if the method is successful, it returns the product in the HTTP response. But if the requested product is not found, the HTTP response contains an HttpError in the request body. The response might look like the following:ConsoleCopy

HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
Date: Thu, 09 Aug 2012 23:27:18 GMT
Content-Length: 51

{
  "Message": "Product with id = 12 not found"
}

Notice that the HttpError was serialized to JSON in this example. One advantage of using HttpError is that it goes through the same content-negotiation and serialization process as any other strongly-typed model.

Exception Filters

 This is an attribute so we can decorate both action method and controller with this. Exception filter is very similar to HandleErrorAttribute in MVC.

This filter is executed when an action method throws the unhandled exception. Note that exception filter does not catch HttpResponseException exception because HttpResponseException is specifically designed to return the HTTP response.

public class CustomExceptionFilter: ExceptionFilterAttribute  
    {  
        public override void OnException(HttpActionExecutedContextactionExecutedContext)  
        {  
            string exceptionMessage = string.Empty;  
            if (actionExecutedContext.Exception.InnerException == null)  
            {  
                exceptionMessage = actionExecutedContext.Exception.Message;  
            }  
            else  
            {  
                exceptionMessage = actionExecutedContext.Exception.InnerException.Message;  
            }  
            //We can log this exception message to the file or database.  
            var response = newHttpResponseMessage(HttpStatusCode.InternalServerError)  
            {  
                Content = newStringContent(“An unhandled exception was thrown by service.”),  
                    ReasonPhrase = "Internal Server Error.Please Contact your Administrator."  
            };  
            actionExecutedContext.Response = response;  
        }  
    }  

Register Exception Filters

There are many ways to register exception filter but the developers generally follow three approaches to register filter.

  • Decorate Action with exception filter.
  • Decorate Controller with exception filter.
  • Filter Register globally.

Using Exception Handlers

Normally, exception filter is used to catch the unhandled exception. This approach will work fine but it fails if any error is raised from outside action. For example, if any error is raised in the following area then exception filter will not work.

  • Error inside the exception filter.
  • Exception related to routing.
  • Error inside the Message Handlers class.
  • Error in Controller Constructor.

Error Handling in ASP.NET Core Web API

using System;
using LoggerService;
using Microsoft.AspNetCore.Mvc;

namespace GlobalErrorHandling.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private ILoggerManager _logger;

        public ValuesController(ILoggerManager logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public IActionResult Get()
        {
            try
            {
                _logger.LogInfo("Fetching all the Students from the storage");

                var students = DataManager.GetAllStudents(); //simulation for the data base access

                _logger.LogInfo($"Returning {students.Count} students.");

                return Ok(students);
            }
            catch (Exception ex)
            {
                _logger.LogError($"Something went wrong: {ex}");
                return StatusCode(500, "Internal server error");
            }
        }
    }
}

So, this works just fine. But the downside of this approach is that we need to repeat our try-catch blocks in all the actions in which we want to catch unhandled exceptions. Well, there is a better approach to do that.

Handling Errors Globally with the Built-In Middleware