Quick Answer
For the lazy people out there:
Install-Package MagicalUnicornMvcErrorToolkit -Version 1.0
Then remove this line from global.asax
GlobalFilters.Filters.Add(new HandleErrorAttribute());
And this is only for IIS7+ and IIS Express.
It won't work if you're using Cassini
Long, explained answer
I know this has been answered. But the answer is really simple (cheers to David Fowler and Damian Edwards for really answering this).
There is no need to do anything custom. For ASP.NET MVC3, all the bits and pieces are there.
Step 1 -> Update your web.config in TWO spots.
<system.web>
<customErrors mode="On" defaultRedirect="/ServerError">
<error statusCode="404" redirect="/NotFound" />
</customErrors>
and
<system.webServer>
<httpErrors errorMode="Custom">
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="500" path="/ServerError" responseMode="ExecuteURL" />
</httpErrors>
...
<system.webServer>
...
</system.web>
Now take careful note of the routes I've decided to use. You can use anything, but my routes are
/NotFound
<- for a 404 not found, error page.
/ServerError
<- for any other error, include errors that happen in my code. this is a 500 Internal Server Error
See how the first section in <system.web>
only has one custom entry? The statusCode="404"
entry? I've only listed one status code because all other errors, including the 500 Server Error
(ie. those pesky error that happens when your code has a bug and crashes the user's request) .. all the other errors are handled by the setting defaultRedirect="/ServerError"
.. which says, if you are not a 404 page not found, then please goto the route /ServerError
.
OK, that's out of the way; now to my routes listed in global.asax
Step 2 - Creating the routes in Global.asax
Here's my full route section:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{*favicon}", new {favicon = @"(.*/)?favicon.ico(/.*)?"});
routes.MapRoute(
"Error - 404",
"NotFound",
new { controller = "Error", action = "NotFound" }
);
routes.MapRoute(
"Error - 500",
"ServerError",
new { controller = "Error", action = "ServerError"}
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new {controller = "Home", action = "Index", id = UrlParameter.Optional}
);
}
That lists two ignore routes -> axd's
and favicons
. Then I have my two explicit error handling routes .. followed by any other routes. In this case, the default one. Of course, I have more, but that's special to my web site. (Just make sure the error routes are at the top of the list. Order is imperative).
Finally, while we are inside our global.asax
file, we do not globally register the HandleError attribute.
Remove this line from global.asax
GlobalFilters.Filters.Add(new HandleErrorAttribute());
Step 3 - Create the controller with the action methods
Now .. we add a controller with two action methods ...
public class ErrorController : Controller
{
public ActionResult NotFound()
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
return View();
}
public ActionResult ServerError()
{
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
// Todo: Pass the exception into the view model, which you can make.
// That's an exercise, dear reader, for -you-.
// In case you want to pass it to the view, if you're admin, etc.
// if (User.IsAdmin) // <-- I just made that up :)
// {
// var exception = Server.GetLastError();
// // etc..
// }
return View();
}
// Secret test method
public ActionResult ThrowError()
{
throw new NotImplementedException("Pew ^ Pew");
}
}
OK, let's check this out. First of all, there is NO [HandleError]
attribute here. Why? Because the built in ASP.NET
framework is already handling errors AND we have specified all the things we need to do to handle an error. It's in this method!
Next, I have the two action methods. Nothing tough there. If you wish to show any exception info, then you can use Server.GetLastError()
to get that info.
Bonus: Yes, I made a third action method, to test error handling.
Step 4 - Create the Views
And finally, create two views. Put them in the normal view spot, for this controller.
Bonus comments
- You don't need an
Application_Error(object sender, EventArgs e)
- The above steps all work 100% perfectly with Elmah
And that, my friends, should be it.