Exception Handling in ASP.NET Web API

Exceptions are the errors that happen at runtime. Exception handling is the technique to handle this runtime error in our application code.

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. There are many ways to handle the exception. In this article, I will explain how we can use Exception Filters.

Exception Filters

We can customize how Web API handles exceptions by writing an exception filter. This can be used to handle unhandled exceptions which are generated in Web API. An exception filter is executed when a controller method throws any unhandled exception that is not an HttpResponseException exception. Exception filters implement the System.Web.Http.Filters.IExceptionFilter interface. 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 override void OnException(HttpActionExecutedContextactionExecutedContext)  
                {  
                    string exceptionMessage = string.Empty;  
                    if (actionExecutedContext.Exception.InnerException == null)  
                    {  
                        exceptionMessage = actionExecutedContext.Exception.Message;  
                    }  
                    else  
                    {  
                        exceptionMessage = actionExecutedContext.Exception.InnerException.Message;  
                    } 
                    var response = newHttpResponseMessage(HttpStatusCode.InternalServerError)  
                    {  
                        Content = newStringContent(“Oops!! Something went wrong, Please Contact your Administrator.”),  
                            ReasonPhrase = "Internal Server Error."  
                    };  
                    actionExecutedContext.Response = response;  
                }  
        

Once the exception filter is created, we need to register the exception filter. There are many ways to register an exception filter, but we generally follow three methods.

  • By action
  • By controller
  • Globally

I have created a custom exception filter recently and the same is given below. We will register this custom exception filter globally. In this filter we have an option to handle our custom exceptions that is thrown from our functions.

 

                    /// 
                    ///     Represents the an attribute that provides a filter for unhandled exceptions.
                    /// 
                    public class CustomExceptionFilter : ExceptionFilterAttribute
                    {
                        #region DefaultHandler
                
                        /// 
                        ///     Gets a delegate method that returns an 
                        ///     that describes the supplied exception.
                        /// 
                        /// 
                        ///     A  delegate method that returns
                        ///     an  that describes the supplied exception.
                        /// 
                        private static readonly Func<Exception, HttpRequestMessage, HttpResponseMessage> DefaultHandler =
                            (exception, request) =>
                            {
                                if (exception == null) return null;
                
                                var response = request.CreateResponse(
                                    HttpStatusCode.InternalServerError, GetContentOf(exception)
                                );
                
                                return response;
                            };
                
                        #endregion DefaultHandler
                
                        #region GetContentOf
                
                        /// 
                        ///     Gets a delegate method that extracts information from the specified exception.
                        /// 
                        /// 
                        ///     A  delegate method that extracts information
                        ///     from the specified exception.
                        /// 
                        private static readonly Func<Exception, string> GetContentOf = exception =>
                        {
                            if (exception == null) return string.Empty;
                
                            var result = new StringBuilder();
                
                            result.AppendLine(exception.Message);
                            result.AppendLine();
                
                            var innerException = exception.InnerException;
                            while (innerException != null)
                            {
                                result.AppendLine(innerException.Message);
                                result.AppendLine();
                                innerException = innerException.InnerException;
                            }
                
                            if (IsDebug) result.AppendLine(exception.StackTrace);
                
                            return result.ToString().TrimEnd(Environment.NewLine.ToCharArray());
                            ;
                        };
                
                        #endregion GetContentOf
                
                        #region Properties
                
                        /// 
                        ///     If true,it will display the stacktrace along with response
                        /// 
                        private static bool IsDebug { get; set; }
                
                        #endregion
                
                        #region OnException(HttpActionExecutedContext actionExecutedContext)
                
                        /// 
                        ///     Raises the exception event.
                        /// 
                        /// The context for the action.
                        public override void OnException(HttpActionExecutedContext actionExecutedContext)
                        {
                            if (actionExecutedContext == null || actionExecutedContext.Exception == null) return;
                
                            var type = LookUpException(actionExecutedContext.Exception).GetType();
                
                            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> registration;
                
                            if (Handlers.TryGetValue(type, out registration))
                            {
                                var statusCode = registration.Item1;
                                var handler = registration.Item2;
                
                                var response = handler(
                                    actionExecutedContext.Exception.GetBaseException(),
                                    actionExecutedContext.Request
                                );
                
                                // Use registered status code if available
                                if (statusCode.HasValue)
                                {
                                    response.StatusCode = statusCode.Value;
                                    //Comment this line if we want to show the error message in reason phrase also.
                                    response.ReasonPhrase = statusCode.ToString().Replace(Environment.NewLine, string.Empty);
                                }
                
                                actionExecutedContext.Response = response;
                            }
                            else
                            {
                                // If no exception handler registered for the exception type, fallback to default handler
                                actionExecutedContext.Response = DefaultHandler(
                                    actionExecutedContext.Exception.GetBaseException(), actionExecutedContext.Request
                                );
                            }
                            
                            base.OnException(actionExecutedContext);
                        }
                
                        #endregion OnException(HttpActionExecutedContext actionExecutedContext)
                
                        #region Register<TException>(HttpStatusCode statusCode)
                
                        /// 
                        ///     Registers an exception handler that returns the specified status code for exceptions of type
                        ///     <typeparamref name="TException" />.
                        /// 
                        /// The type of exception to register a handler for.
                        /// The HTTP status code to return for exceptions of type <typeparamref name="TException" />.
                        /// 
                        ///     This  after the exception handler has been added.
                        /// 
                        public CustomExceptionFilter Register<TException>(HttpStatusCode statusCode)
                            where TException : Exception
                        {
                            var type = typeof(TException);
                            var item = new Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>(
                                statusCode, DefaultHandler
                            );
                
                            if (!Handlers.TryAdd(type, item))
                            {
                                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> oldItem;
                
                                if (Handlers.TryRemove(type, out oldItem)) Handlers.TryAdd(type, item);
                            }
                
                            return this;
                        }
                
                        #endregion Register<TException>(HttpStatusCode statusCode)
                
                        #region Register<TException>(Func<Exception, HttpRequestMessage, HttpResponseMessage> handler)
                
                        /// 
                        ///     Registers the specified exception  for exceptions of type
                        ///     .
                        /// 
                        /// The type of exception to register the  for.
                        /// The exception handler responsible for exceptions of type <typeparamref name="TException" />.
                        /// 
                        ///     This  after the exception 
                        ///     has been added.
                        /// 
                        /// The  is .
                        public CustomExceptionFilter Register<TException>(
                            Func<Exception, HttpRequestMessage, HttpResponseMessage> handler)
                            where TException : Exception
                        {
                            if (handler == null) throw new ArgumentNullException("handler");
                
                            var type = typeof(TException);
                            var item = new Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>(
                                null, handler
                            );
                
                            if (!Handlers.TryAdd(type, item))
                            {
                                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> oldItem;
                
                                if (Handlers.TryRemove(type, out oldItem)) Handlers.TryAdd(type, item);
                            }
                
                            return this;
                        }
                
                        #endregion Register<TException>(Func<Exception, HttpRequestMessage, HttpResponseMessage> handler)
                
                        #region Unregister<TException>()
                
                        /// 
                        ///     Unregisters the exception handler for exceptions of type <typeparamref name="TException" />.
                        /// 
                        /// The type of exception to unregister handlers for.
                        /// 
                        ///     This  after the exception handler
                        ///     for exceptions of type <typeparamref name="TException" /> has been removed.
                        /// 
                        public CustomExceptionFilter Unregister<TException>()
                            where TException : Exception
                        {
                            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> item;
                
                            Handlers.TryRemove(typeof(TException), out item);
                
                            return this;
                        }
                
                        #endregion Unregister<TException>()
                
                        #region Check for registered exceptions
                
                        /// 
                        ///     Check for registered exceptions
                        /// 
                        /// 
                        /// Exception
                        private Exception LookUpException(Exception exception)
                        {
                            var e = exception;
                            var type = exception.GetType();
                            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> registration;
                            if (Handlers.TryGetValue(type, out registration)) return e;
                            while (exception.InnerException != null)
                            {
                                exception = exception.InnerException;
                                type = exception.GetType();
                                if (Handlers.TryGetValue(type, out registration))
                                {
                                    e = exception;
                                    break;
                                }
                            }
                
                            return e;
                        }
                
                        #endregion
                
                        #region UnhandledExceptionFilterAttribute()
                
                        /// 
                        ///     Initializes a new instance of the  class.
                        /// 
                        public CustomExceptionFilter()
                        {
                        }
                
                        public CustomExceptionFilter(bool isDebug)
                        {
                            IsDebug = isDebug;
                        }
                
                        #endregion UnhandledExceptionFilterAttribute()
                
                        #region Handlers
                
                        /// 
                        ///     Gets the exception handlers registered with this filter.
                        /// 
                        /// 
                        ///     A  collection that contains
                        ///     the exception handlers registered with this filter.
                        /// 
                        protected
                            ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>>
                            Handlers { get; } =
                            new ConcurrentDictionary<Type,
                                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>>();
                
                        #endregion Handlers
                    }
                

 

The above custom exception filter can be registered globally in WebApiConfig / startup class like below.

 

                    /// 
                    ///     Function to register all the custom exceptions
                    /// 
                    /// 
                    private static void RegisterExceptionFilter(HttpConfiguration config)
                    {
                        var enableStackTrace = true;
                        //Registering exception filters
                        config.Filters.Add(new CustomExceptionFilter(enableStackTrace)
                            .Register<NoRecordFoundException>(HttpStatusCode.NotFound)
                            .Register<InvalidRequestException>(HttpStatusCode.BadRequest)
                            .Register<InvalidDataOperation>(HttpStatusCode.PreconditionFailed)                            
                        );
                    }
       

 

Comments

Popular Posts