Implementing Error Handling in ASP.NET MVC

In this post I am going to try to document the solution to the following problem. I would like to implement custom global error handling in my ASP.NET MVC application using entity framework to log errors into the database.  Once the error is logged, I want to redirect the user to the custom page that will contain the newly generated error number so that they can contact support and give them more information about the error.

I am going to start with defining a custom error table in my DbContext.  In my case I am going to keep it simple, but you can add any custom columns you would like as long as you can get to the data from your executing application.

Here is how I am going to define my table and context

 

using System;


using System.ComponentModel.DataAnnotations;


namespace MvcEFCodeFirst.Data

{

   
public class AppError


    {

       
public int AppErrorID { get; set; }

       
public DateTime ErrorDateTime { get; set; }

        [
StringLength(250)]

       
public string UserIdentity { get; set; }

       
public string ErrorText { get; set; }

    }

}

Of course, you can add any other columns you would like that suits your use case.  Then I add this table to my context via a new property:

public DbSet<AppError> AppErrors { get; set; }

Now, I am going to write the key part of the solution – my global error handler object.  It must do the following tasks:

  • Get the last error information
  • Log it into the table above
  • Redirect to the controller that will show a message to the user along with newly generated error (incident) number

The class is pretty simple, and works mostly on HttpContext.Current class to get the information it needs:

using System;


using System.Web;

using System.Web.Mvc;


using System.Web.Routing;


using MvcEFCodeFirst.Controllers;


using MvcEFCodeFirst.Data;


using MvcEFCodeFirst.DataAccess;





namespace MvcEFCodeFirst.Helpers


{


   
public static class ErrorHandler


    {


       
public static void HandleError()


        {





           
try


           


               
// get the last error


                var exception = HttpContext.Current.Server.GetLastError();


               
// clear out error information and any pending responses


                HttpContext.Current.Server.ClearError();


               
HttpContext.Current.Response.Clear();


               
// create new error to log


                var error = new AppError


                {


                    ErrorDateTime =
DateTime.Now


                    ErrorText = exception.ToString(),


                    UserIdentity =
HttpContext.Current.User.Identity.Name


                }


               
// log error


                using (var context = new CodeStockContext())


                {


                    error = context.AppErrors.Add(error)


                    context.SaveChanges()


                }


               
// redirect to error ErrorController.Index method


                // and pass in application error object so that the controller


                // could add error number to the page


                var routeData = new RouteData();


                routeData.Values.Add(
"controller", "Error");


                routeData.Values.Add(
"action", "Index");


                routeData.Values.Add(
"appError", error);





                ((
IController)new ErrorController())


                    .Execute(
new RequestContext(


                       
new HttpContextWrapper(HttpContext.Current), routeData));


            }


           
// ReSharper disable EmptyGeneralCatchClause


            catch (Exception)


           
// ReSharper restore EmptyGeneralCatchClause


           


               
//Ignore errors that occur during loggin


           


        }


    }


}

I added comments to demonstrate what I am doing in this class.

Now. let me show you my error controller, which is very simple and the Index view for it:

using System.Web.Mvc;


using MvcEFCodeFirst.Data;





namespace MvcEFCodeFirst.Controllers


{


   
public class ErrorController : Controller


    {





       
public ActionResult Index(AppError appError)


        {


           
return View(appError);


        }





    }


}

Now, the view

@using MvcEFCodeFirst.Data


@model
AppError


<h2>


    Error has occurred</h2>


<
h4>


    An error has occurred and has been logged. Error number is @Model.AppErrorID.ToString().


    Please contact technical support.


</h4>


<
br /


@Html.ActionLink("Main Menu", "", "")



And a few last steps.  I turn off custom errors in web config since I an implementing them differently now:

<system.web>

    <customErrors mode="Off"/>

</system.web>

And the last step is to call my error handler from Global.asax error handling routine:

using System;

using System.Web.Mvc;

using System.Web.Routing;

using System.Data.Entity;

using MvcEFCodeFirst.DataAccess;

using MvcEFCodeFirst.Helpers;

 
namespace MvcEFCodeFirst

{

    public class MvcApplication : System.Web.HttpApplication

    {

        public static void RegisterGlobalFilters(GlobalFilterCollection filters)

        {

            filters.Add(new HandleErrorAttribute());

        }

 
        public static void RegisterRoutes(RouteCollection routes)

        {

            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

 
            routes.MapRoute(

                "Default",

                "{controller}/{action}/{id}",

                new { controller = "Home", action = "Index", id = UrlParameter.Optional }

            );

 
        }

 
        protected void Application_Start()

        {

            AreaRegistration.RegisterAllAreas();

 
            RegisterGlobalFilters(GlobalFilters.Filters);

            RegisterRoutes(RouteTable.Routes);

 
            Database.SetInitializer(new DbInitializer());

        }

 
 

        protected void Application_Error(object sender, EventArgs e)

        {


           
ErrorHandler.HandleError();


        }


    }

}

And that is all there is to it.  I know there is a bunch of other solutions, such as ELMAH, I saw while investigating my possible answers to the problem, but I think this one suits my goals the best.  Of course, you can also retrieve the error code from the exception by casting it to HttpException, but in my case I did not need to do that.  Another option is to use Response.Redirect instead of Controller.Execute, but this is a small variation of the same solution.

Thanks.

One Comment

Leave a Reply to David Cancel reply

Your email address will not be published. Required fields are marked *