Custom Error Handling In MVC 5

Creating a custom error page in MVC 5 is not too difficult.  If you want to have one that is server farm friendly, well that is not too tough either.  I created a little demo project for doing custom errors that passes a model around.  To be effective in handling uncaught errors we will need to use the Global.asax Application_Error method.  You can also use this method to send errors that you catch but just cannot handle or fail out of gracefully.

There are a number of reasons why you may want to do custom errors like I do below.

  • You are in a server farm or other environment where Session based objects will not work
  • Display a user friendly message based on the error received
  • Display a user friendly message based on an HTTP Code (if one was returned)
  • Log the error
  • Send a notification of the error

Create the Model

Our model will take the actual error message, a user friendly message, and the HTTP Code.  I would caution not to send too much to your error model, or any data you would not want your client to see.  It needs to be light weight since it will more than likely be sent as query strings in the URL.  In a real situation I would not send the actual error message, but remember, this is just a little demo.

public class ErrorObject
{
    public string ErrorMessage { get; set; }
    public string FriendlyMessage { get; set; }
    public int HttpCode { get; set; }
}

Controller And Views

Now we need a controller and a view to play with our errors.  I will not do anything real fancy, I will just let Visual Studio scaffold my custom error page (or view).  My index view will be used to throw an error so we can test things.

Controller

public class TestErrorsController : Controller
{
    // GET: TestErrors
    public ActionResult Index()
    {
        throw new Exception("This is a test error!", new HttpException(500, "Internal Sheldon Error!"));
        return View();
    }

    public ActionResult DisplayError(ErrorObject oError)
    {
        return View(oError);
    }
}

DisplayErrors View

@model MVCSandbox.Models.ErrorObject

@{
    ViewBag.Title = "DisplayError";
}

<h2>DisplayError</h2>

<div>
    <h4>ErrorObject</h4>
    <hr />
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.ErrorMessage)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.ErrorMessage)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.FriendlyMessage)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.FriendlyMessage)
        </dd>

        <dt>
            @Html.DisplayNameFor(model => model.HttpCode)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.HttpCode)
        </dd>

    </dl>
</div>
<p>
    @Html.ActionLink("Edit", "Edit", new { /* id = Model.PrimaryKey */ }) |
    @Html.ActionLink("Back to List", "Index")
</p>

 Map a New Route

I felt it would be easier to redirect to a named route.  To map this new route we need to edit the RouteConfig.cs inside the App_Start folder.  Normally you would want to put your custom routes before the default one, but since I plan to only call this one by name I will just add it to the end.

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

        routes.MapRoute(
            name: "Error",
            url: "TestErrors/DisplayError/{oError}",
            defaults: new { oError = UrlParameter.Optional }
        );
    }
}

Edit the Application_Error Method

The real work is done in the Application_Error method inside the Global.asax.  You will notice that in the controller I added an inner exception, I really did this to show that you can and should look for inner exceptions.  In my case I just used it to hold an HTTP Code and a little message.  This is also where you would more than likely perform logging and notification.

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }

    protected void Application_Error(object sender, EventArgs e)
    {
        ErrorObject oError = new ErrorObject();
        Exception oException = Server.GetLastError();
        Exception innerException = oException.InnerException;
        HttpException httpException = oException as HttpException;
        if(httpException == null && innerException != null)
        {
            httpException = innerException as HttpException;
        }
        Response.Clear();

        oError.ErrorMessage = oException.Message;
        oError.FriendlyMessage = "Ooops!  There was a problem!";
        oError.HttpCode = (httpException != null) ? httpException.GetHttpCode() : 0;

        Server.ClearError();

        Response.RedirectToRoute("Error", oError);
    }
}

 The Result

Finally here is the result.

The view rendered by DisplayErrors.

The view rendered by DisplayErrors.

Advertisements

About SheldonS

Web developer for over 15 years mainly with Microsoft technologies from classic ASP to .NET 4. Husband, father, and aspiring amateur photographer.

Posted on February 25, 2015, in MVC and tagged , . Bookmark the permalink. Leave a comment.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: