ASP.NET MVC Error Handling using the HandleError Filter

ASP.NET Errors can be effectively handled using the HandleError filter, which specifies how exceptions which are thrown in a controller method are to be dealt with (note that ASP.NET MVC supports method filters, which allow for annotating controller methods to change their behavior).
Prior to using the HandleError filter, you will need to enable custom error handling for your ASP.NET MVC app in the web.config file by adding the below line in the system.web section:

<customErrors mode="On" />

The below code snippet show the HandleError filter being applied to the controller class. Thus, any exception which is thrown by an action method in the controller will be handled using the custom error policy.

namespace MVCApplicationDemo.Controllers {
[HandleError]
public class ProductController : Controller {

When the HandleError filter is applied without arguments, any exception thrown by the methods covered by the filter will result in the Views/Shared/Error.aspx view being used which just shows the message “Sorry, an error occurred while processing your request.” To test
this process, attempt to view details of a product which does not yet exist, such as with a URL as below:

http://localhost:51823/Product/Details/999990

The error message can be more specific and you may wish to give the user more detailed info. To do this create a view in the Views/Shared folder named NoRecordErr.aspx with the below content:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>" %>
<asp:Content ID="errTitle" ContentPlaceHolderID="TitleContent" runat="server">
Error Occured
</asp:Content>
<asp:Content ID="errContent" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Sorry, but that record does not yet exist.
</h2>
</asp:Content>

This is a modification of the default error view with a message to show the user that the product request does not yet exist in the database. Next, we can apply the HandleError filter to the Details controller method as in the below snippet:

[HandleError(View="NoRecordErr")]
public ActionResult Details(int id) {

The filter informs the MVC framework that it should use the NoRecordErr view for an exception that is thrown by the Details method. Now, you need to modify the backstop filter to be as show below:

 [HandleError(Order=2)]
public class ProductController : Controller {

An Order value of 2 ensures the controller-wide filter is to be applied only in the event that there is not a HandleError filter with a higher order available. If you do not set a value for Order, the default error view takes precedence.
Now if a user attempts to requests details for a product which does not exist they will be served the new error view. The default error view will be served if other controller methods throw exceptions.

However as it currently stands a user will be informed that they requested a non-existent record for any exception arising from the Details method. Thus you will need to be even more specific.
First, create an exception in the ProductController class which will be used when a record the user has requested is not available:

class NoRecordErr : Exception {
}

The next step is to modify the Details method so that it explicitly checks whether the LINQ query returned a record and if not record was returned throw a new exception.

[HandleError(View="NoRecordErr",
ExceptionType=typeof(NoRecordErr))]
public ActionResult Details(int id) {
NorthwindEntities db = new NorthwindEntities();
var data = db.Products.Where(e => e.ProductID == id).Select(e => e);
if (data.Count() == 0) {
throw new NoRecordErr();
} else {
Product record = data.Single();
return View(record);
}
}

In summery we changed the HandleError filter by including a value for the ExceptionType property, then specifying the type of the exception the filter should to apply to. Now when a NoRecordErr() exception is thrown, the NoRecordErr.aspx view will instead be used. Alternatively, the generic Error.aspx will be used for other exception types (since we applied the controller-wide backstop filter).