Handle the custom exception in ASP.NET MVC

Introduction

The ASP.NET exception is not user friendly, so we should create our custom exception handler, but for some situation, we maybe didn’t handle the error and user will see the ASP.NET build error. For this case, I will introduce how to handle all of the system exception(even you didn’t use try…catch), user will see the friendly error message.

Using the code

1. For handle all of the exception, we need to override the OnException for handle the exception, so we can create a BaseController to do it:

protected override void OnException(ExceptionContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException("context");
    }
	
    //If exception handled before, do nothing.
    //If this is child action, exception should be handled by main action.
    if (context.ExceptionHandled || context.IsChildAction)
    {
        base.OnException(context);
        return;
    }
	
    // If custom errors are disabled, we need to let the normal ASP.NET exception handler
    // execute so that the user can see useful debugging information.
    if (!context.HttpContext.IsCustomErrorEnabled)
    {
        base.OnException(context);
        return;
    }
	
    // If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
    // ignore it.
    if (new HttpException(null, context.Exception).GetHttpCode() != 500)
    {
        base.OnException(context);
        return;
    }
	
    //We handled the exception!
    context.ExceptionHandled = true;
	
    //Return a special error response to the client.
    context.HttpContext.Response.Clear();
	
    // Certain versions of IIS will sometimes use their own error page when
    // they detect a server error. Setting this property indicates that we
    // want it to try to render ASP.NET MVC's error page instead.
    context.HttpContext.Response.TrySkipIisCustomErrors = true;
	
    // update the error status code
    context.HttpContext.Response.StatusCode = 500;
	
    // Pass the exception data to error controller
    TempData["exception"] = context.Exception;
	
    context.Result = this.RedirectToAction("CustomError", "Error");
}

2. Implement the Application_Error event in Global.asax.cs

protected void Application_Error(Object sender, EventArgs e)
{
    Exception ex = HttpContext.Current.Server.GetLastError();

    bool handleUnknownError = Convert.ToBoolean(WebConfigurationManager.AppSettings["HandleUnknownError"]);
    if (handleUnknownError)
    {
        //var routeData = new RouteData();
        //routeData.Values["controller"] = "Error";
        //routeData.Values["action"] = "CustomError";
        Response.StatusCode = 500;

        //HttpContext.Current.Session["exception"] = ex;

        ExceptionHandler.LogException(); //we need to create the exception handler for handle the exception

        UrlHelper url = new UrlHelper(HttpContext.Current.Request.RequestContext);
        var redirectUrl = url.RouteUrl(new { Controller = "Error", Action = "CustomErrorFromGlobal" });
        HttpContext.Current.Response.Redirect(redirectUrl);
    }
}

3.Create an exception handler:

public static class ExceptionHandler
{
    private static ErrorViewModel error; //the error view model for save the exception information

    public static ErrorViewModel GetLastError
    {
        get
        {
            HttpContext.Current.Server.ClearError();
            return error;
        }
    }

    public static void LogException()
    {
        error = new ErrorViewModel();
        var errInfo = new ErrorInfo();
        errInfo.Message = "Unknow Error";
        Exception exc = HttpContext.Current.Server.GetLastError();
        if (exc == null) return;
        string errLog = "";
        errLog += "**********" + DateTime.Now + "**********<br>";

        if (exc.InnerException != null)
        {
            errLog += "Inner Exception Type: <br>";
            errLog += exc.InnerException.GetType() + "<br>";
            errLog += "Inner Exception: " + "<br>";
            errLog += exc.InnerException.Message + "<br>";
            errLog += "Inner Source: ";
            errLog += exc.InnerException.Source + "<br>";
            if (exc.InnerException.StackTrace != null)
            {
                errLog += "\nInner Stack Trace: " + "<br>";
                errLog += exc.InnerException.StackTrace + "<br>";
            }
        }

        errLog += "Exception Type: " + exc.GetType().ToString() + "<br>";

        if (exc.StackTrace != null)
        {
            errLog += "\nStack Trace: " + "<br>";
            errLog += exc.StackTrace + "<br>";
        }

        errInfo.Message = exc.Message;
        errInfo.Details = errLog;

        error.ErrorInfo = errInfo;
    }
}

4.Create an error controller for redirect and show the error page, and in this controller:

public class ErrorController : BaseController
{  
    public ActionResult CustomErrorFromGlobal()
    {
        return View("Error", ExceptionHandler.GetLastError);
    }

}

5.Show the error message in the view (put the Error.cshtml view file in the shared folder):

<div class="panel-body">
    <h3>Error Message</h3>
    <p style="color:red;">
         @Html.Raw(Model.ErrorInfo.Message)
    </p>
    <h3>Details</h3>
    <p>
        @Html.Raw(Model.ErrorInfo.Details)
    </p>
    @* Show validation errors *@
    @if (Model.ErrorInfo.ValidationErrors != null && Model.ErrorInfo.ValidationErrors.Length > 0)
    {
        <ul>
            @foreach (var validationError in Model.ErrorInfo.ValidationErrors)
            {
                <li>
                    @validationError.Message
                    @if (validationError.Members != null && validationError.Members.Any())
                    {
                        <text>(@string.Join(", ", validationError.Members))</text>
                    }
                </li>
            }
        </ul>
    }

    <h3>Error URL</h3>
    <p>
        @refUrl
    </p>
</div>

6.Create a controller for testing:

public ActionResult Error1()
{
    //in this action, I didn't do any exception handler
    List<string> test = null;

    test.Add("a");

    return View("Index");
}

public ActionResult Error2()
{    
    try
    {
        List<string> test = null;

        test.Add("a");
    }
    catch (Exception ex)
    {
        //for this case, it will show below error message for user
        throw new Exception("Object is not defined!");
    }
    return View("Index");
}

Download the full source code:

CoderBlogExceptionHandlerDemo.zip (20 downloads)

2,663 total views, 14 views today

Share your vote!


Do you like this post?
  • Fascinated
  • Happy
  • Sad
  • Angry
  • Bored
  • Afraid

You may also like...

shares