Exception Handling (2), In ASP.NET MVC Web API

Exception handling is required in any application. It is a very interesing issue where different apps have their own various way(s) to handle that. I plan to write a series of articles to discuss this issue
 
In this article, we will be discussing various ways of handling an exception in ASP.NET MVC Web API.
 

Introduction

 
For error handling, the major difference between ASP.NET MVC Web API from MVC or a Web application is that MVC or a Web app will return a web page to handle the error while Web API is to return a RESTfule output, JSON. In ASP.NET MVC Web API, we may have three ways to handle exceptions:
  • HttpResponseException, associated with HttpStatusCode, --- Locally in Try-catch-final
  • Exception filter, --- in the level of Action, Controller, and Globally
  • Exception Handler, --- Globally

Note:

When you try to create a ASP.NET Web API app, the MVC is selected automatically. So, the Web API app actually is an added on one of the MVC app. Therefore, we have discussed the features of exception handling for ASP.NET MVC will still hold, such as:

  • Try-catch-finally: Step 3
  • Exception filter
    • Default: customError mode="On" in Web.Config, Step 1-1
      • Associated with [HandleError] attribute): Step 1-2
      • Add statusCode in Web.Config: Step 1-3
  • Application_Error event: Step 4
We will set up a test sample app first, and then discuss the three above one by onve.
 

Set up the test sample app

 
Step 1 - Create an ASP.NET MVC Web API app
 
We use the current version of Visual Studio 2019 16.8 and .NET Framework 4.7.2 to build the app.
  • Start Visual Studio and select Create a new project.
  • In the Create a new project dialog, select ASP.NET Web Application (.NET Framework) > Next.
  • In the Configure your new project dialog, enter ErrorHandlingWebAPI for Project name > Create.
  • In the Create a new ASP.NET Web Application dialog, select Web API > Creat

Note:

When you try to create a ASP.NET Web API app, the MVC is selected automatically. So, the Web API app actually is an added on part of the MVC app.

Different from MVC app, the Web API (associated with MVC) has two more files added:

While the WebApiConfig.cs file defines the class WebApiConfig, including the WebApi routing: 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;

namespace ErrorHandlingWebAPI_1
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}
the ValuesController.cs defines a sample ApiController:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace ErrorHandlingWebAPI_1.Controllers
{
    public class ValuesController : ApiController
    {
        // GET api/values
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        public string Get(int id)
        {
            return "value";
        }

        // POST api/values
        public void Post([FromBody] string value)
        {
        }

        // PUT api/values/5
        public void Put(int id, [FromBody] string value)
        {
        }

        // DELETE api/values/5
        public void Delete(int id)
        {
        }
    }
}

Build and run the app, you will see the following image shows the app,

 
Step 2 - Add one empty apiController
 
Add one empty apiController into the app, with the name as ErrorHandlingWebAPIController:
  • Right click Controllers > add > controller.
  • In the Add New Scaffolded Item dialog, select Web API in the left pane, and
  • Web API 2 Controller - Empty > Add.
  • In the Add Controller dialog, Change ErrorHandlingWebAPIController for controller name > Add.
Step 3 - Add Swagger Client
 
To test Web API, we could use Postman or Fiddler, here we use Swagger that can test the Web API automatically, and actually is by default installed for the Web API Core. 
 
Installing Swagger from NuGet Package Manager, from Visual Studio project: 
  • Right click Tools > NuGet Package Manager > Manage NuGet for Solution
  • In the opened NuGet-Solution dialog: Click Browse, then Search Swagger
  • Install Swashbuckle v5.6.0
 
 
After installation, and App_Start folder, there will be a file, SwaggerConfig.cs, added with code,
public class SwaggerConfig  
{  
    public static void Register()  
    {  
        var thisAssembly = typeof(SwaggerConfig).Assembly;  
        GlobalConfiguration.Configuration  
          .EnableSwagger(c => c.SingleApiVersion("v1", "ErrorHandlingWebAPI"))  
          .EnableSwaggerUi();   
    }  
} 

Step 4 - Change the default start up page to the swagger

Right click Project > Properties to open the properties page, then choose Web, in Start Url, add /swagger,
 
We will demonstrate the result from the next section, after adding a web api controller.
 

HttpResponseException

 
Add code into controller
 
We add the following code into the controller,
[Route("CheckId/{id}")]  
[HttpGet]     
public IHttpActionResult CheckId(int id)  
{         
    if (id < 10) // No error hanbdling at all:  
    {  
        int a = 1;  
        int b = 0;  
        int c = 0;  
        c = a / b; //it would cause exception.     
    }  
    else if (id <20) // Error handling by HttpResponseException with HttpStatusCode  
    {  
        throw new HttpResponseException(HttpStatusCode.BadRequest);  
    }  
    else if (id < 30) // Error handling by HttpResponseException with HttpResponseMessage  
    {  
        var response = new HttpResponseMessage(HttpStatusCode.BadRequest)  
        {  
            Content = new StringContent(string.Format("No Employee found with ID = {0}", 10)),  
            ReasonPhrase = "Employee Not Found"  
        };  
  
        throw new HttpResponseException(response);  
    }  
  
    return Ok(id);  
}  

Run the app, we have 

 

Discussion

 
Without Error Handling
 
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.
 
In our sample, // No error hanbdling at all case: we choos id = 1
 
 
The result in Swagger: only indicated that "An error has occurred." without detailed info,, and the response code is 500 --- Internal Server Error,
 
 
Error handled by HttpResponseException
 
The HttpResponseException type is a special case. This exception returns any HTTP status code that you specify in the exception constructor. For our sample, // Error handling by HttpResponseException with HttpStatusCode, the returns 400, BadRequest, if the id = 11.
 
 
For more control over the response, you can also construct the entire response message and include it with the HttpResponseException,
 // Error handling by HttpResponseException with HttpResponseMessage, id = 21
 
 
Note
if you use Postman as a client, you can get one more piece of info: Employee Not Found associated with error code 400
 
 

Exception Filters

 
Exception Eilter
 
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.
 
Web API Exception filters implement the System.Web.Http.Filters.IExceptionFilter interface, which is different from MVC filters that implement System.Web.mvc.IExceptionFilter Interface that is derived from both

Therefore, for Web API, there is no more local exception filter defined under ApiController as OnException method as MVC Controller does.

The simplest way to write an exception filter is to derive from the System.Web.Http.Filters.ExceptionFilterAttribute class and override the OnException method. 
public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute  
{  
    public override void OnException(HttpActionExecutedContext context)  
    {  
        if (context.Exception is NotImplementedException)  
        {  
            var resp = new HttpResponseMessage(HttpStatusCode.NotFound)  
            {  
                Content = new StringContent("This method is not implemented"),  
                ReasonPhrase = "Not implemented"  
            };  
            throw new HttpResponseException(resp);  
        }  
    }  
} 

Register Exception Eilter

Exception filter must be registered in order to be used. There are three ways to register a Web API exception filter,
  • By action
  • By controller
  • Globally
We add another apiController named as TestController with code,
using System;  
using System.Web.Http;  
  
namespace ErrorHandlingWebAPI.Controllers  
{  
    [NotImplExceptionFilter]
    public class TestController : ApiController  
    {  
        [Route("Test/{id}")]  
        [HttpGet]  
        [NotImplExceptionFilter]  
        public IHttpActionResult Test(int id)  
        {  
            throw new NotImplementedException("This method is not implemented");  
        }  
  
        [Route("Test1/{id}")]  
        [HttpGet]  
          
        public IHttpActionResult Test1(int id)  
        {  
            throw new NotImplementedException("This method is not implemented");  
        }  
    }  
}  

Add attribute [NotImplExceptionFilter] in Test action level or in the controller level, or globally register in App_Start/WebApiConfig file to add: config.Filters.Add(new NotImplExceptionFilterAttribute());

public static class WebApiConfig  
{  
    public static void Register(HttpConfiguration config)  
    {  
        // Web API configuration and services  
        config.Filters.Add(new NotImplExceptionFilterAttribute());  
  
        // Web API routes  
        config.MapHttpAttributeRoutes();  
  
        config.Routes.MapHttpRoute(  
            name: "DefaultApi",  
            routeTemplate: "api/{controller}/{id}",  
            defaults: new { id = RouteParameter.Optional }  
        );  
    }  
}  

Note:

For ASP.NET MVC, there are three configuration files:

For ASP.NET Web API, there are four configuration files:

  • For MVC, When we derive an exception filter class from HandleErrorAttribute class, then we need to register it in FilterConfig.cs file, add into GlobalFilerCollection. While on the other hand,
  • For Web API, When we derive an exception filter class from ExceptionFilterAttribute class, then we need to register it in WebApiConfig.cs file, add into HttpConfiguration.

We add one more option in the previous controller/action: ErrorHandlingWebAPIController/CheckID to test the global registered exception filter

else if (id < 50)  
{  
    throw new NotImplementedException();  
}  

Then, run the app, and test the results,

 
 This is the for action level test result,
 
 

Exception Handler

Unhandled exceptions can be processed via exception filters, but there are a number of cases that exception filters can't handle. For example,
  1. Exceptions thrown from controller constructors.
  2. Exceptions thrown from message handlers.
  3. Exceptions thrown during routing.
  4. Exceptions thrown during response content serialization.
Those could be handled by Exception Handler.

We add a global error handler into Global.asax file,

public class GlobalExceptionHandler : ExceptionHandler  
{  
    public override void Handle(ExceptionHandlerContext context)  
    {  
        var response = new HttpResponseMessage(HttpStatusCode.BadRequest)  
        {  
            Content = new StringContent("This is handled by Global Exception Handler"),  
            ReasonPhrase = "Exception Handler"  
        };  
  
        context.Result = new ResponseMessageResult(response);  
    }  
}  

and register in App_Start/WebApiConfig file,

public static void Register(HttpConfiguration config)  
{  
    // Web API configuration and services  
    config.Filters.Add(new NotImplExceptionFilterAttribute());  
    config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());  
  
    // Web API routes  
    config.MapHttpAttributeRoutes();  
  
    config.Routes.MapHttpRoute(  
        name: "DefaultApi",  
        routeTemplate: "api/{controller}/{id}",  
        defaults: new { id = RouteParameter.Optional }  
    );  
}  

Add one more test option in the testController

using System;  
using System.Web.Http;  
  
namespace ErrorHandlingWebAPI.Controllers  
{  
    [NotImplExceptionFilter]  
    public class TestController : ApiController  
    {  
        [Route("Test/{id}")]  
        [HttpGet]  
        [NotImplExceptionFilter]  
        public IHttpActionResult Test(int id)  
        {  
            throw new NotImplementedException("This method is not implemented");  
        }  
  
        [Route("Test1/{id}")]  
        [HttpGet]  
          
        public IHttpActionResult Test1(int id)  
        {  
            throw new NotImplementedException("This method is not implemented");  
        }  
  
        [Route("Test2/{id}")]  
        [HttpGet]  
  
        public IHttpActionResult Test2(int id)  
        {  
            throw new DivideByZeroException();  
        }  
    }  
}  

Due to the exception DivideByZeroException() not handled by exception filter we made privously, it will be caught by the global error handler,

 

Summary

 
In ASP.NET MVC Web API, we usually have three ways to handle exceptions,
  • HttpResponseException, associated with HttpStatusCode, --- Locally in Try-catch-final
  • Exception filter, --- in the level of Action, Controller, and Globally
  • Exception Handler, --- Globally
 The order to exection is HttpResponseException ==> Exception filter ==> Exception Handler.
 
References


Similar Articles