Although usually the attribute / ActionFilter approach is
the way to go, you may find that you want to have more control over how errors
are handled, or you want to be able to more easily test that an Exception
occurred within your controller. In this case, you may want to create a custom
ViewResult for exceptions. Listing 4 shows an ErrorViewResult class created
for this purpose.
Listing 4
public class ErrorViewResult : ViewResult
{
public Exception Exception { get; private set; }
public ErrorViewResult(Exception exception)
{
Exception = exception;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.StatusCode = 500;
context.HttpContext.Response.TrySkipIisCustomErrors = true;
if(context.HttpContext.IsCustomErrorEnabled)
{
base.ViewName = "Error";
base.ExecuteResult(context);
return;
}
string controllerName = (string)context.RouteData.Values["controller"];
string actionName = (string)context.RouteData.Values["action"];
HandleErrorInfo info = new HandleErrorInfo(Exception, controllerName,
actionName);
ViewData.Model = info;
if (String.IsNullOrEmpty(base.ViewName))
{
base.ViewName = "ErrorDetails";
}
base.ExecuteResult(context);
}
}
It expects an Exception in its constructor, and its
ExecuteResult() method handles the rest of the work. Note that it will respect
the <customErrors /> section and if this is turned on, it will default to
simply rendering the Error view. You can remove or reconfigure this bit of
logic if you'd like for this to work regardless of how customErrors are
configured. Otherwise, it simply wraps up the Exception that was passed in
along with the controller and action names into a
System.Web.Mvc.HandleErrorInfo object, which it then passes to a strongly typed
ErrorDetails view. Listing 5 shows this View:
Listing 5 - ErrorDetails.cshtml
<span style='background:yellow'>@model </span>System.Web.Mvc.HandleErrorInfo
<span style='background:yellow'>@{</span
Layout = null>;
<span style='background:yellow'>}</span>
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
</head>
<body>
<h2>
Sorry, an error occurred while processing your request.
</h2>
<b>Error: </b>
<p>Controller: <span style='background: yellow'>@</span>Model.ControllerName</p>
<p>Action: <span style='background:yellow'>@</span>Model.ActionName</p>
<p><span style='background:yellow'>@</span>Model.Exception.Message</p>
<p><span style='background:yellow'>@</span>Model.Exception.ToString()</p>
</body>
</html>
Setting up a call to the ErrorViewResult would typically
occur in the catch {} block, as Listing 6 shows.
Listing 6 - Returning an ErrorViewResult from an
Action
public ActionResult CreateErrorWithErrorViewResult()
{
try
{
throw new ApplicationException("Something bad happened.");
}
catch (Exception ex)
{
return new ErrorViewResult(ex);
}
return View();
}
The result of this action, when <customerErrors />
mode is Off, is shown here:
Naturally you can extend the functionality of the
ErrorViewResult as needed, for instance if you wanted to be able to pass in the
name of the View to use when rendering exceptions.
If you need to log exceptions, probably the best way to do
so today is with ELMAH. You'll find some useful code showing how
to wire up ELMAH with ASP.NET MVC here. It shows how to subclass the
HandleError attribute and hook into its OnException event to control what
happens when an exception occurs.