We are just about to release a new version of our corporate website, and one of the last things I needed to do was handle 404’s gracefully. We did a major overhaul of the information architecture, so there is a very good chance that some people will get 404’s from existing bookmarks.
Turns out, it all starts in Global.asax.
Unlike a Web Forms app, where a url translates to a file on disk, MVC uses a set of routes to invoke methods on controller. So, when a request comes in with a bogus Url, say http://dtsams.com/bogus, the app actually throws an exception because it’s trying to instantiate BogusController, which of course is not part of the application. To catch this exception, we implement Application_Error in Global.asax
protected void Application_Error() { var exception = Server.GetLastError(); var httpException = exception as HttpException; Response.Clear(); Server.ClearError(); var routeData = new RouteData(); routeData.Values["controller"] = "Errors"; routeData.Values["action"] = "General"; routeData.Values["exception"] = exception; Response.StatusCode = 500; if (httpException != null) { Response.StatusCode = httpException.GetHttpCode(); switch (Response.StatusCode) { case 403: routeData.Values["action"] = "Http403"; break; case 404: routeData.Values["action"] = "Http404"; break; } } // Avoid IIS7 getting in the middle Response.TrySkipIisCustomErrors = true; IController errorsController = new ErrorsController(); HttpContextWrapper wrapper = new HttpContextWrapper(Context); var rc = new RequestContext(wrapper, routeData); errorsController.Execute(rc); }
So, looking at this, we basically get the exception, clear the response, setup some new RouteData, look at the exception, and then send things off the the Errors controller.
Of note, the Response.TrySkipIisCustomErrors line is critical when you deploy to IIS 7.5, and it took me a fair bit of Googling and hacking to finally get this working. Without this line, I’d get routed to the Errors controller correctly on my local IIS, but I’d just get the IIS 404 screen when I pushed to the production web server.
So, clearly this means we need an Errors controller – this is a simple example – 3 actions, just dropping to views.
public class ErrorsController : Controller { public ActionResult General(Exception exception) { return View("Exception",exception); } public ActionResult Http404() { return View("404"); } public ActionResult Http403() { return View("403"); } }
You’ll likely want to add more logic to log real exceptions, and record the 404’s so that if there is a common 404, you can add some additional logic to make recommendations, or automatically forward the user to the correct location.
While this is relatively simple stuff, for some reason it’s not baked into the project templates – yet another thing to add into our templates I guess!
Thanks very much, this was exactly what I was looking for
Awesome solution, just what I wanted. To take things a step further, you can reduce the code in Global.asax and make it testable, ie correct RouteData, Response is cleared etc…
Wrap the context, _container.Resolve().Handle(new HttpContextWrapper(Context)), sort of thing.
In my case, the ExceptionHandler.Handle then loops through a dynamically registered list of IExceptionHandlers. If an IExceptionHandler can handle the exception it does. This may or may not mean executing the ErrorController (Redirects). If none of them can handle it, you fall back to a generic solution. Can also inject an ILog for logging.
Nice post, is there a way to clear the broken url from the address bar from the global? Thanks,
In that case, I think you’d want to redirect them to the home page or something like that. You could show this 404 page, and in there use a meta-refresh header to forward them to the homepage (or wherever) after a few seconds.
Cheers,
Dave
This sulution doesn’t work form me on IIS. Only working with Development Server.
OMG I have sought for this for two hours now.
Been on so many forums saying one thing, another thing, and all without any of it working.
This is simple to understand, easy to implement, and works like a charm!
BRAVO Dave!
Finally a working post!
I have been searching for this for 2 hours now, and been to (I think) every possible forum on the living web all saying different things – all without working.
This is easy to understand, fast to implement and it works like a charm as well.
BRAVO Dave!
I used it on csharpudvikler.dk to make a 404 Page that can help to find missing children (Notfound.org).
Wow, your solution DOES ACTUALLY WORK
Just as westdk I’ve read a lot of posts/threads about this issue no other solution I’ve found has been working as good as your’s. Thanks a lot!
Great! Glad it helped!
Cheers,
Dave
This is the only solution that works for me as desired
Thank you vey much!
This one will give a 302 on the error pages, right? So, i thought that should be prevented, for SEO…