I have an MVC 2 application that should always give a \'nice\' 404 page.
However currently I get a low level .Net one: \"Server Error in \'/sitename\' Applicatio
Setting it up at web.config
level does not "just redirect to the page". On an MVC app, you give it a "{controller/action}"
url an it will actually call that action:
<customErrors mode="On" defaultRedirect="/system/problem">
<error statusCode="404" redirect="/system/notfound" />
</customErrors>
This will call the NotFound
on the SystemController
.
In your action you can then for instance get the value of HttpContext.Request.RawUrl
to see what the faulty request was: "/system/notfound?aspxerrorpath=/Invalid"
. In this case I tried to go to the InvalidController
.
A nice way to handle this things, by the way, is implementing ELMAH (or Error Logging Modules and Handlers. Scott Hanselman wrote an "introductory" post about it, but ELMAH is nowadays available as a NuGet package.
You might want to take a look at this question/ansers on how to use it with ASP.NET MVC: How to get ELMAH to work with ASP.NET MVC [HandleError] attribute?
I finally found the answer to this, though it's still not ideal.
You can restrict the controller names that are allowed to match a route using a regex, so if we assume the default implementation of the controller factory we can figure out all the possible class names that are supported:
// build up a list of known controllers, so that we don't let users hit ones that don't exist
var allMvcControllers =
from t in typeof(Global).Assembly.GetTypes()
where t != null &&
t.IsPublic &&
!t.IsAbstract &&
t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
typeof(IController).IsAssignableFrom(t)
select t.Name.Substring(0, t.Name.Length - 10);
// create a route constraint that requires the controller to be one of the reflected class names
var controllerConstraint = new
{
controller = "(" + string.Join("|", allMvcControllers.ToArray()) + ")"
};
// default MVC route
routes.MapRoute(
"MVC",
"{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
controllerConstraint);
// fall back route for unmatched patterns or invalid controller names
routes.MapRoute(
"Catch All",
"{*url}",
new { controller = "System", action = "NotFound" });
This isn't ideal, it adds a hit on the application start and still feels far too complicated, but it does have the desired effect.