March 22, 2012 06:56 by
Scott
In this blog post I will show how to set up custom error pages in ASP.NET MVC 3 applications to create user-friendly error messages instead of the (yellow) IIS default error pages for both “normal” (non-AJAX) requests and jQuery AJAX requests.
In this showcase we will implement custom error pages to handle the HTTP error codes 404 (“Not Found”) and 500 (“Internal server error”) which I think are the most common errors that could occur in web applications. In a first step we will set up the custom error pages to handle errors occurring in “normal” non-AJAX requests and in a second step we add a little JavaScript jQuery code that handles jQuery AJAX errors.
We start with a new (empty) ASP.NET MVC 3 project and activate custom errors in the Web.config by adding the following lines under <system.web>:
<customErrors mode="On" defaultRedirect="/Error">
<error redirect="/Error/NotFound" statusCode="404"/>
<error redirect="/Error/InternalServerError" statusCode="500"/>
</customErrors>
Note: You can set mode=”Off” to disable custom errors which could be helpful while developing or debugging. Setting mode=”RemoteOnly” activates custom errors only for remote clients, i.e. disables custom errors when accessing via http://localhost/[...]. In this example setting mode=”On” is fine since we want to test our custom errors. You can find more information about the <customErrors> element here.
In a next step we remove the following line in Global.asax.cs file:
filters.Add(new HandleErrorAttribute());
and add a new ErrorController (Controllers/ErrorController.cs):
public class ErrorController : Controller
{
public ActionResult Index()
{
return InternalServerError();
}
public ActionResult NotFound()
{
Response.TrySkipIisCustomErrors = true;
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View("NotFound");
}
public ActionResult InternalServerError()
{
Response.TrySkipIisCustomErrors = true;
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return View("InternalServerError");
}
}
In a last step we add the ErrorController‘s views (Views/Error/NotFound.cshtml and Views/Error/InternalServerError.cshtml) that defines the (error) pages the end user will see in case of an error. The views include a partial view defined in Views/Shared/Error/NotFoundInfo.cshtml respectively Views/Shared/Error/InternalServerErrorInfo.cshtml that contains the concrete error messages. As we will see below using these partial views enables us to reuse the same error messages to handle AJAX errors.
Views/Error/NotFound.cshtml:
@{
ViewBag.Title = "Not found";
}
@{
Html.RenderPartial("Error/NotFoundInfo");
}
Views/Shared/Error/NotFoundInfo.cshtml:
The URL you have requested was not found.
Views/Error/InternalServerError.cshtml:
@{
ViewBag.Title = "Internal server error";
}
@{
Html.RenderPartial("Error/InternalServerErrorInfo");
}
Views/Shared/Error/InternalServerErrorInfo.cshtml:
An internal Server error occured.
To handle errors occurring in (jQuery) AJAX calls we will use jQuery UI to show a dialog containing the error messages. In order to include jQuery UI we need to add two lines to Views/Shared/_Layout.cshtml:
<link href="@Url.Content("~/Content/themes/base/jquery.ui.all.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.min.js")" type="text/javascript"></script>
Moreover we add the following jQuery JavaScript code (defining the global AJAX error handling) and the Razor snippet (defining the dialog containers) to Views/Shared/_Layout.cshtml:
< script type="text/javascript">
$(function () {
// Initialize dialogs ...
var dialogOptions = {
autoOpen: false,
draggable: false,
modal: true,
resizable: false,
title: "Error",
closeOnEscape: false,
open: function () { $(".ui-dialog-titlebar-close").hide(); }, // Hide close button
buttons: [{
text: "Close",
click: function () { $(this).dialog("close"); }
}]
};
$("#InternalServerErrorDialog").dialog(dialogOptions);
$("#NotFoundInfoDialog").dialog(dialogOptions);
// Set up AJAX error handling ...
$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError)
{
if (jqXHR.status == 404) {
$("#NotFoundInfoDialog").dialog("open");
} else if (jqXHR.status == 500) {
$("#InternalServerErrorDialog").dialog("open");
} else {
alert("Something unexpected happend :( ...");
}
});
});
</ script>
<div id="NotFoundInfoDialog">
@{ Html.RenderPartial("Error/NotFoundInfo"); }
</div>
<div id="InternalServerErrorDialog">
@{ Html.RenderPartial("Error/InternalServerErrorInfo"); }
</div>
As you can see in the Razor snippet above we reuse the error texts defined in the partial views saved in Views/Shared/Error/.
To test our custom errors we define the HomeController (Controllers/HomeController.cs) as follows:
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Error500()
{
throw new Exception();
}
}
and the corresponding view Views/Home/Index.cshtml:
@{
ViewBag.Title = "ViewPage1";
}
<script type="text/javascript">
$function () {
$("a.ajax").click(function (event) {
event.preventDefault();
$.ajax({
url: $(this).attr('href'),
});
});
});
</script>
<ul>
<li>@Html.ActionLink("Error 404 (Not Found)", "Error404")</li>
<li>@Html.ActionLink("Error 404 (Not Found) [AJAX]", "Error404", new { },
new { Class = "ajax" })</li>
<li>@Html.ActionLink("Error 500 (Internal Server Error)", "Error500")</li>
<li>@Html.ActionLink("Error 500 (Internal Server Error) [AJAX]", "Error500", new { }, new { Class = "ajax" })</li>
</ul>
To test the custom errors you can launch the project and click one of the four links defined in the view above. The “AJAX links” should open a dialog containing the error message and the “non-AJAX” links should redirect to a new page showing the same error message.
Summarized this blog post shows how to set up custom errors that handle errors occurring in both AJAX requests and “non-AJAX” requests. Depending on the project, one could customize the example code shown above to handle other HTTP errors as well or to show more customized error messages or dialogs.