ASP.NET MVC - How to throw a 404 page similar to that on StackOverflow
I've currently got a BaseController class that inherits from System.Web.Mvc.Controller
. On that class I have the HandleError
Attribute that redirects users to the "500 - Oops, we screwed up" page. This is currently working as expected.
THIS WORKS
<HandleError()> _
Public Class BaseController : Inherits System.Web.Mvc.Controller
''# do stuff
End Class
I also have my 404 pages working on a Per-ActionResult basis which is again working as expected.
THIS WORKS
Function Details(ByVal id As Integer) As ActionResult
Dim user As Domain.User = UserService.GetUserByID(id)
If Not user Is Nothing Then
Dim userviewmodel As Domain.UserViewModel = New Domain.UserViewModel(user)
Return View(userviewmodel)
Else
''# Because of RESTful URL's, some people will want to "hunt around"
''# for other users by entering numbers into the address. We need to
''# gracefully redirect them to a not found page if the user doesn't
''# exist.
Response.StatusCode = CInt(HttpStatusCode.NotFound)
Return View("NotFound")
End If
End Function
Again, this works great. If a user enters something like http://example.com/user/999 (where userID 999 doesn't exist), they will see the appropriate 404 page, and yet the URL will not change (they are not redirected to an error page).
I CAN'T GET THIS IDEA TO WORK
Here's where I'm having an issue. If a user enters http://example.com/asdf- they get kicked over to the generic 404开发者_JAVA技巧 page. What I want to do is leave the URL in tact (IE: not redirect to any other page), but simply display the "NotFound" view as well as push the HttpStatusCode.NotFound
to the client.
For an example, just visit https://stackoverflow.com/asdf where you'll see the custom 404 page and see the URL left in tact.
Obviously I'm missing something, but I can't figure it out. Since "asdf" doesn't actually point to any controller, my base controller class isn't kicking in, so I can't do it in the "HandleError" filter in there.
Thanks in advance for the help.
Note: I absolutely do not want to redirect the user to a 404 page. I want them to stay at the existing URL, and I want MVC to push the 404 VIEW to the user.
Edit:
I have also tried the following to no avail.
Shared Sub RegisterRoutes(ByVal routes As RouteCollection)
routes.RouteExistingFiles = False
routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
routes.IgnoreRoute("Assets/{*pathInfo}")
routes.IgnoreRoute("{*robotstxt}", New With {.robotstxt = "(.*/)?robots.txt(/.*)?"})
routes.AddCombresRoute("Combres")
''# MapRoute allows for a dynamic UserDetails ID
routes.MapRouteLowercase("UserProfile", _
"Users/{id}/{slug}", _
New With {.controller = "Users", .action = "Details", .slug = UrlParameter.Optional}, _
New With {.id = "\d+"} _
)
''# Default Catch All Valid Routes
routes.MapRouteLowercase( _
"Default", _
"{controller}/{action}/{id}/{slug}", _
New With {.controller = "Events", .action = "Index", .id = UrlParameter.Optional, .slug = UrlParameter.Optional} _
)
''# Catch All InValid (NotFound) Routes
routes.MapRoute( _
"NotFound", _
"{*url}", _
New With {.controller = "Error", .action = "NotFound"})
End Sub
My "NotFound" route is doing nothing.
Found my answer on my other SO question. Thanks very much Anh-Kiet Ngo for the solution.
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
// A good location for any error logging, otherwise, do it inside of the error controller.
Response.Clear();
HttpException httpException = exception as HttpException;
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "YourErrorController");
if (httpException != null)
{
if (httpException.GetHttpCode() == 404)
{
routeData.Values.Add("action", "YourErrorAction");
// We can pass the exception to the Action as well, something like
// routeData.Values.Add("error", exception);
// Clear the error, otherwise, we will always get the default error page.
Server.ClearError();
// Call the controller with the route
IController errorController = new ApplicationName.Controllers.YourErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
}
}
You could try :
<customErrors mode="On" redirectMode="ResponseRewrite">
<error statusCode="404" redirect="~/Error.aspx"/>
</customErrors>
http://msdn.microsoft.com/en-us/library/system.web.configuration.customerrorssection.redirectmode.aspx
If the RedirectMode property is set to ResponseRewrite, the user is sent to error page and the original URL in the browser is not changed.
Routes are pattern matches. Your not found route isn't working because the pattern of your incorrect URL matches an earlier route.
So:
''# Default Catch All Valid Routes
routes.MapRouteLowercase( _
"Default", _
"{controller}/{action}/{id}/{slug}", _
New With {.controller = "Events", .action = "Index", .id = UrlParameter.Optional, .slug = UrlParameter.Optional} _
)
''# Catch All InValid (NotFound) Routes
routes.MapRoute( _
"NotFound", _
"{*url}", _
New With {.controller = "Error", .action = "NotFound"})
If you enter: http://example.com/asdf- then that matches the "Default" route above - MVC goes looking for asdf-Controller
and can't find it, so exception thrown.
If you go to http://example.com/asdf-/action/id/slug/extra then "Default" no longer matches and the "NotFound" route will be followed instead.
You could add a filter for all your known controllers to the "Default" route, but that's going to be a pain to maintain when you add new controllers.
You shouldn't need to hook into the Application_Error
event at all, but I haven't found a better way around the missing controller yet - I asked it as another question.
I got this to work by simply returning the action defined in the base controller, rather than redirecting to it.
[HandleError]
public ActionResult NotFound()
{
Response.StatusCode = 404;
Response.TrySkipIisCustomErrors = true;
return View("PageNotFound", SearchUtilities.GetPageNotFoundModel(HttpContext.Request.RawUrl));
}
(The "SearchUtilities.GetPageNotFoundModel" bit is about generating suggestions by feeding the URL into our search engine)
In any action method that inherits the base, I can simply call this:
return NotFound();
... whenever I catch an invalid parameter. And the catch-all route does the same.
Set it up in the web config:
<system.web>
<customErrors mode="On" defaultRedirect="error.html"/>
</system.web>
error.html is your custom page that you can have to display any arbitrary HTML to the user.
It's big miss from ASP.NET MVC framework because you can't customize (real customization I mean not just-one-error-page-for-anything) 404 pages without "crunches" like 1 answer. You can use one 404 error for all not-found causes, or you can customize multiple 500 error pages via [HandleError], but you CAN NOT customize 404 errs declaratively. 500 errs are bad due to SEO reasons. 404 errs are good errs.
精彩评论