Veloxcore blog | Always Discovering |Always Sharing

Always Discovering. Always Sharing.

25 Mar 16

Exception Handling in MVC

Exceptions, we as a developer can’t neglect them, they are like bacteria, they can survive even the hardest tester, they are ancient, they come all the way without getting faded. Let’s see how we can handle exceptions the right and not so right way.


exception in mvc

Exceptions, we as a developer can’t neglect them, they are like bacteria, they can survive even the hardest tester, they are ancient, they come all the way without getting faded and now a days we are getting away from testers making developer more responsible to their code, we have to harness our code to handle every kind of exception keeping user experience in mind. So what to do, handle all exception, simple period. But I’d like to have backup strategy rather than going upfront with shield and sward. So let’s see how we can handle exceptions the right and not so right way. 

Try...catch

Simple easy way, just put try catch everywhere. Do whatever you want to do in catch block, log it, email it or just swallow it. Below code illustrates it,

public ActionResult Index()
{
    try
    {
        // ... bla bla bla
        return View();
    }
    catch (Exception ex)
    {
        // Do something here with ex (and if you are lazy like me, continue reading to next topic)
        return View("Error");
    }
}

Isn't that lovely, it is but everything comes with a price and here we pay for reusability and who likes to write try catch in every action method, just too much work. So let's check out another way, let's try to use base class to make some code reusable.

Override OnException controller method

MVC provide a easy way to trap in all unhandled exception in a controller, for that you just need to override one method named OnException and it looks like below,

protected override void OnException(ExceptionContext filterContext)
{
    Exception lastException = filterContext.Exception;
    // Do something with exception, hint log it somewhere.
    filterContext.ExceptionHandled = true;      // So that no one else will be bothered
    filterContext.Result = new ViewResult() { ViewName = "Error" };
}

That's looks nice, we can handle all excception in controller, even send the custom error view also to browser. But this is limited to single controller and response HTTP status is 200 which is wrong and search engine will take it valid page and crawl it. Hence we switch to more generic exception handling, i.e Global Exception handling.

Global level Exception Handling

For this to work we need to do some modification to web.config. In system.web section add customerErrors section like and then add a controller named Error with Index action returning error view. Here we are asking asp.net to capture all unhandled exceptions and redirect it to some path. We can even specify individual http error type and their respective redirect path. But this also is not not optimal solution, I can’t do any custom processing in this case. Also everything is wrapped under the hood making things unclear for beginners. So let’s explore MVC feature called action filters, specifically HandleErrorAttribute.

GlobalFilter to handle errors

We can use out of the box HandleErrorAttribute to handle unhandled exceptions. You can decorate your controller or add it to global action filters to work with whole website.

[HandleError(ExceptionType=typeof(DivideByZeroException), View="Error")]
[HandleError(ExceptionType=typeof(NullReferenceException), View="CustomErrorPage")]
public class MyController : IController { ... }

But (here it comes again), but this has limitation too, like we can't log excception or email it to admin, exceptions raised outside of the controller won't be handled and it won't handle AJAX excception smoothly. So here comes the solution to all problems(finally a solution I like).

Extending HandleErrorAttribute

This method allows us to capture all unhandled events, and sort of act as Application_Error in asp.net. Without boring you much lets dive into code,

public class HandleAjaxExceptionAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest() && filterContext.Exception != null)
        {
            // Log exception first
            filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
            JsonResult result = new JsonResult { JsonRequestBehavior = JsonRequestBehavior.AllowGet };
            JavaScriptSerializer json_serializer = new JavaScriptSerializer();
            result.Data = new
            {
                Message = "Error Processing your request. Technical detail: " + filterContext.Exception.Message,
                TechnicalDetails = filterContext.Exception.Message + System.Environment.NewLine + filterContext.Exception.StackTrace
            };
            filterContext.Result = result;
            filterContext.ExceptionHandled = true;
        }
        else
        {
            base.OnException(filterContext);
        }
    }
}

You might be thinking I'm not handling any exception other than that occured while Ajax call. Because that I'd like to handle in Global.asax. That is convention and I find it pretty ready for other developer to understand it even if they are not aware of MVC. In above code we are sending some data back to browser so that it can show relevant information when Ajax call fails. Ideally you should never send technical details back to browser but I think for development purpose it is kind of useful hence kept in code sample. And we can do that pretty easily using ajax.setup. Typical code look likes below,

$.ajaxSetup({
    error: function (xhr, status, error) {
        if (xhr.status == 401) {
            // perform a redirect to the login page since we're no longer authorized
        }
        else if (xhr.responseJSON != null) {
            $('#AjaxErrorHolder').append("<div class='alert alert-danger alert-dismissable'>" +
                "<button type='button' class='close' data-dismiss='alert' aria-hidden='true' >&times;</button>" +
                "<span class='glyphicon glyphicon-exclamation-sign'></span>&nbsp;Oops!!! " + xhr.responseJSON.Message + "</div>");
        }
    },
    cache: false
});

This gives us following nice looking UI (thanks to bootstrap too),

Custom error handling for ajax call in MVC with bootstrap One benefit of this approach is that we give feedback to user that something went wrong in the Ajax call they made. Let's see how Global.asax looks like.

class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        GlobalFilters.Filters.Add(new HandleAjaxExceptionAttribute());
    }
    protected void Application_Error(object sender, EventArgs e)
    {
        // Check we are in Ajax request or normal one
        HttpContext httpContext = ((MvcApplication)sender).Context;
        HttpRequestWrapper httpRequest = new HttpRequestWrapper(httpContext.Request);

        // Log whatever the error is
        object ex = Server.GetLastError();

        if (!httpRequest.IsAjaxRequest())
        {
            if (true)
            {
                string currentController = "";
                string currentAction = "";
                RouteData currentRouteData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));
                if (!(currentRouteData == null))
                {
                    if (currentRouteData.Values["controller"] != null && 
				!String.IsNullOrEmpty(currentRouteData.Values["controller"].ToString()))
                    {
                        currentController = currentRouteData.Values["controller"].ToString();
                    }
                    if (currentRouteData.Values["action"] != null && 
				!String.IsNullOrEmpty(currentRouteData.Values["action"].ToString()))
                    {
                        currentAction = currentRouteData.Values["action"].ToString();
                    }
                }

                ErrorController controller = new ErrorController();
                string action = "Index";
                RouteData routeData = new RouteData();
                if (ex is HttpException)
                {
                    HttpException httpEx = ex as HttpException;
                    switch (httpEx.GetHttpCode())
                    {
                        case 404:
                            action = "NotFound";
                            break;
                    }
                }
                httpContext.ClearError();
                httpContext.Response.Clear();
                httpContext.Response.StatusCode = ex is HttpException ? ((HttpException)ex).GetHttpCode() : 500;
                httpContext.Response.TrySkipIisCustomErrors = true;

                routeData.Values["controller"] = "Error";
                routeData.Values["action"] = action;
                routeData.Values["area"] = "";

                ((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData));
            }
        }
    }
}

Application_Error will be called in all exceptions other than Ajax call. Also it gives use one more benefit, this code shows error/notfound page but URL is kept the same when error occurred or page not found. Also if you see the HTTP status it is 404 for not found pages hence search engines won’t crawl them. So at the end it is win win solution for us.

You can find full source code for the blog post at Veloxcore Library

Like reading it, Share it.