12

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 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.

asked Aug 7, 2010 at 16:39

6 Answers 6

9

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));
 }
 }
}
answered Aug 19, 2010 at 21:03
Sign up to request clarification or add additional context in comments.

2 Comments

This is a good solution however before errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData)); is called, this line needs to be added Response.StatusCode = 404; If this line is not added, the page response is still a 200 regardless of what is rendered for the user.
IController.Execute is protected, at least in MVC 4, so I just had to expose it wrap it from my controller: public void Exec(RequestContext requestContext) { this.Execute(requestContext); }
3

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.

answered Aug 19, 2010 at 21:11

4 Comments

ResponseRewrite has issues sometimes if the error is in a WebForms page and the redirect is an MVC one and vice versa.
@Keith, could you expand on this? I'm running into these cases and I want to try to understand why it only works some times...
@RaulVejar not really, this is an old post (I think the problem was with MVC2) and we just avoided it with different custom errors for MVC and WebForms
@Keith, hate to say it but there is still issues in MVC4. We had to end up handling errors manually on the global asax and turn of custom errors completely
1

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.

answered Apr 18, 2011 at 11:14

Comments

1

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.

answered Oct 18, 2011 at 10:39

4 Comments

This is ok, but I'm now able to do it without the need for a NotFound action. Makes things much cleaner.
I see. That's appealing. How do you deal with routes that get matched, therefore don't throw 404, but get passed invalid parameters?
I have a custom HandleNotFoundException that I can throw from a valid controller. It will load up the appropriate shared view Shared/NotFound.cshtml but still keep the URI intact. throw HandleNotFoundException
My answer above is out of date... I need to update it with my solution.
0

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.

answered Aug 7, 2010 at 16:45

4 Comments

Like I said, I do NOT want to redirect the user.
What if you set up a route in the routing dictionary that had unknown routes route to your error controller/actionresult and returned the view you wanted. That would leave the URL in tact.
hmm, what would a route like that look like?
-1 because the answer "Redirects" which is precisely what I DON'T want.
0

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.

answered Dec 23, 2010 at 8:57

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.