ASP.NET MVC - Catch All Route And Default Route
Asked Answered
E

7

77

In trying to get my application to produce 404 errors correctly, I have implemented a catch all route at the end of my route table, as shown below:

 routes.MapRoute(
            "NotFound", _
           "{*url}", _
           New With {.controller = "Error", .action = "PageNotFound"} _
       )

However, to get this working, I had to remove the default route:

{controller}/action/{id}

But now that the default has been removed, most of my action links no longer work, and the only way I have found to get them working again is to add individual routes for each controller/action.

Is there a simpler way of doing this, rather than adding a route for each controller/action?

Is it possible to create a default route that still allows the catch all route to work if the user tries to navigate to an unknown route?

Esoteric answered 22/10, 2010 at 21:25 Comment(8)
Why do you think 404 was not working correctly?Sisco
@quakkels, I have had the same complaint as @Sean. ASP.NET throws a 404 but then does a 302 redirect so the result it not a real 404 on the page in question. This is when using customErrors.Madelenemadelin
@Dustin Yes, this is the very same problem I've found, I dont think this is SEO friendlyEsoteric
In MVC, the url does not always relate to a file. IE: domain.com/path/to/destination is not a file structure. Therefore, a 404 shouldn't be sent because it isn't looking for a file.Sisco
IMO, if I go to domain.com/nopage where nopage is not a valid controller, there should be a 404.Madelenemadelin
totally agree, 404 = Not found, regardless whether its a physical file or notEsoteric
404 is a FILE not found errorSisco
HTTP 404 - "The requested resource could not be found." HTTP / REST is all about resources, and begs the question, what is a 'file'?Sizar
S
115

Use route constraints

In your case you should define your default route {controller}/{action}/{id} and put a constraint on it. Probably related to controller names or maybe even actions. Then put the catch all one after it and it should work just fine.

So when someone would request a resource that fails a constraint the catch-all route would match the request.

So. Define your default route with route constraints first and then the catch all route after it:

routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = UrlParameter.Optional },
    new { controller = "Home|Settings|General|..." } // this is basically a regular expression
);
routes.MapRoute(
    "NotFound",
    "{*url}",
    new { controller = "Error", action = "PageNotFound" }
);
Soave answered 12/1, 2011 at 11:29 Comment(10)
Good point. I thought the first route would match only if the controller name matches... Thank you!Dorinedorion
@andreirinea no, routing map isn't checking any types. It only checks whether current request's URL matches mapping. The only thing each route checks are constraints when present.Soave
You could use reflection to build your controller constraint and thus avoid a maintenance burden every time you add a new controller. private static string GetAllControllersAsRegex() { var controllers = typeof(MvcApplication).Assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Controller))); var controllerNames = controllers.Select(c => c.Name.Replace("Controller", "")); return string.Format("({0})", string.Join("|", controllerNames)); }Erskine
Is this the correct way to implement 404 error on url that are not resolved by route. Or is it just a workaround and cleaner solution exist. I find it funny that in 2015 you have to manualy handle something that should be done by server.Gobert
@f470071: You're right. In normal conditions this is easily handled by web server but sometimes applications have specific constraints that should allow arbitrary resource querying. The only strange thing that OP wants to do is to handle errors in their app while they could do so using usual web server capabilities. I would also leave out the catch-all route definition at the end and just use constrained routing as outlined in my answer.Soave
@Robert I know this is not the place but could you take a look at my question #30619801. I have a problem that I consistently get 500 error response instead of 404.Gobert
@RobertKoritnik , MVC 5, possible to deny Direct action access only allow route, example action is= AuctionOne/Index route=one/index is it possible to deny action but not route..?!Metcalfe
@Metcalfe I'm sorry, but I don't understand what you're asking? Denying actions is done using AuthorizeAttribute or at least some ActionMethodSelectorAttribute derived class. If that's what you're looking for...Soave
@RobertKoritnik I mean in address sending class name and it action should be restricted but allow it only if it is in rout thank youMetcalfe
@SAR. I'm trying my best to figure out your question... So when you say direct action access what do you mean by that? If routing parses URL to your Controller.Action(...) method, then this action will get executed. In your case (as I understand) URL is .../one/index which points by routing configuration to ActionOneController.Index(...) action method. Which action method would you want to execute when you allow route, but deny action method? Something has to be executed otherwise there will be an exception.Soave
S
25
//this catches all  requests
routes.MapRoute(
    "Error",
    "{*.}",
     new { controller = "PublicDisplay", action = "Error404" } 
);

add this route at the end the routes table

Sofiasofie answered 22/10, 2010 at 21:32 Comment(3)
Surely this catches all request. But if you declare it before the default route, it will override the default route and if you declare it after the default route, it won't be called because the default route catches the request.Krisha
"{*.*}", worked for me on asp.net vb as a catchallRoberson
"{*.}" was only catching routes without an extension for me. Changed to "{*.*}" to catch all routes, including .aspx.Excretion
M
7

Ah, the problem is your default route catches all 3 segment URLs. The issue here is that Routing runs way before we determine who is going to handle the request. Thus any three segment URL will match the default route, even if it ends up later that there's no controller to handle it.

One thing you can do is on your controller override the HandleMissingAction method. You should also use the tag to catch all 404 issues.

Mozellemozes answered 23/10, 2010 at 3:1 Comment(0)
M
3

Well, what I have found is that there is no good way to do this. I have set the redirectMode property of the customErrors to ResponseRewrite.

<customErrors mode="On" defaultRedirect="~/Shared/Error" redirectMode="ResponseRewrite">
    <error statusCode="404" redirect="~/Shared/PageNotFound"/>
</customErrors>

This gives me the sought after behavior, but does not display the formatted page.

To me this is poorly done, as far as SEO goes. However, I feel there is a solution that I am missing as SO does exactly what I want to happen. The URL remains on the failed page and throws a 404. Inspect stackoverflow.com/fail in Firebug.

Madelenemadelin answered 22/10, 2010 at 21:53 Comment(3)
Funny you should say that, when I first started looking at the issue, I first checked to see how SO handles it. I could manually add routes for each action, but I think this would be hard to maintain over time. I really think that MS could provide better guidance on the issue.Esoteric
Maybe if we ask Jeff nicely he will share it with us? :)Esoteric
After much digging around, trying out various solutions, this one seems to work ok for me. #620395Esoteric
A
1

My Solution is 2 steps.

I originally solved this problem by adding this function to my Global.asax.cs file:

protected void Application_Error(Object sender, EventArgs e)

Where I tried casting Server.GetLastError() to a HttpException, and then checked GetHttpCode. This solution is detailed here:

ASP.NET MVC Custom Error Handling Application_Error Global.asax?

This isn't the original source where I got the code. However, this only catches 404 errors which have already been routed. In my case, that ment any 2 level URL.

for instance, these URLs would display the 404 page:

www.site.com/blah

www.site.com/blah/blah

however, www.site.com/blah/blah/blah would simply say page could not be found. Adding your catch all route AFTER all of my other routes solved this:

routes.MapRoute(
            "NotFound",
            "{*url}",
            new { controller = "Errors", action = "Http404" }
        );

However, the NotFound route does not seem to route requests which have file extensions. This does work when they are captured by different routes.

Aracelis answered 2/2, 2012 at 5:25 Comment(0)
S
1

I would recommend this as the most readable version. You need this in your RouteConfig.cs, and a controller called ErrorController.cs, containing an action 'PageNotFound'. This can return a view. Create a PageNotFound.cshtml, and it'll be returned in response to the 404:

        routes.MapRoute(
            name:  "PageNotFound",
            url:  "{*url}",
            defaults: new { controller = "Error", action = "PageNotFound" }
        );

How to read this:

name: "PageNotFound"

= create a new route template, with the arbitrary name 'PageNotFound'

url:"{*url}"

= use this template to map all otherwise unhandled routes

defaults: new { controller = "Error", action = "PageNotFound" }

= define the action that an incorrect path will map to (the 'PageNotFound' Action Method in the Error controller). This is needed since an incorrectly entered path will not obviously not map to any action method

Scutage answered 7/2, 2016 at 23:36 Comment(0)
T
0

I tried all of the above pattern without luck, but finally found out that ASP.NET considered the url I used as a static file, so none of my request was hidding the single controller endpoint. I ended up adding this snipper to the web.config

<modules runAllManagedModulesForAllRequests="true"/>

And then use the below route match pattern, and it solved the issue:

 routes.MapRoute(
            name:  "RouteForAnyRequest",
            url:  "{*url}",
            defaults: new { controller = "RouteForAnyRequest", action = "PageNotFound" }
        );
Talkathon answered 29/11, 2022 at 10:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.