ASP.NET MVC - Catch All Route And Default Route - asp.net-mvc

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?

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" }
);

//this catches all requests
routes.MapRoute(
"Error",
"{*.}",
new { controller = "PublicDisplay", action = "Error404" }
);
add this route at the end the routes table

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.

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.

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.

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

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" }
);

Related

Adding filename in url

I want to customize route in ASP.NET MVC.
With
#Url.Action("ViewDoc", "Home", new { FileName = "ABC.pdf" })
and
routes.MapRoute(
name: "",
url: "{controller}/{action}/{FileName}",
defaults: new
{
controller = "Home",
action = "ViewDoc",
FileName = UrlParameter.Optional
}
I get
http://localhost/Home/ViewDoc?FileName=ABC.pdf
How to get the below?
http://localhost/Home/ViewDoc/ABC.pdf
The code you have pasted is correct but the ordering in your route setup is probably wrong. Move the routes.MapRoute method to be above the default route and it should work as expected.
Regarding your 404 error:
I'm using the same kind of URL with a file name at the end and getting the same routing problem.
Just like you, I try to catch the call with a controller.
I think the problem is the URL is treated as a direct link to a file on the server and it will just try to go get the file instead of calling the controller. Not finding the file at the physical location suggested by the URL will trigger a 404 error.
The workaround I chose to use is adding a "/" character at the very end of the URL after the file name.
There are others.
I suggest you read this related question:
Dots in URL causes 404 with ASP.NET mvc and IIS
I was able get
localhost/Home/ViewDoc/ABC.pdf
with
public FileResult View(string FileName) {
and
routes.MapRoute( "", "Home/ViewDoc/{FileName}", new { controller = "Home", action = "ViewDoc" } );
For Error 404.0 added the below under
<add
name="AdfsMetadata"
path="/Home/ViewDocu/*"
verb="POST"
type="System.Web.Handlers.TransferRequestHandler"
preCondition="integratedMode,runtimeVersionv4.0" />

Default Routing Not working in .NET MVC 1 (and 2)

My default routes are very simple, but the page doesn't properly load without fully qualifying the entire route.
Here are the routes I'm using:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index" } // Parameter defaults
);
Here's the only action in the application in a HomeController:
public ActionResult Index()
{
return Content("New stuff");
}
With these URLs:
http://localhost:8081/NewMvc1/
I get The incoming request does not match any route.
With:
http://localhost:8081/NewMvc1/Home
http://localhost:8081/NewMvc1/Home/Index
I get a 404 Mvc page that says it tried to handle the request with a static file.
Yet, finally with a 'fully qualified url'
http://localhost:8081/NewMvc1/Home/Index/1
I get the expected result output from the one and only one action.
New Stuff
This doesn't seem right at all. I've also been getting Failed to Execute Action from this same application (not sure if that's related).
I've used Phil Haack's RouteDebugger to get this far, which pointed out that it wasn't matching the URL when the Optional parameters were missing, but did when those parameters were provided.
You're missing the id from your defaults:
new { controller = "Home", action = "Index", id = UrlParameter.Optional }

Resource Could Not Be Found

I am creating a website in ASP.NET MVC3. My problem is that I am getting a "Resource Could Not Be Found" error when the file referenced appears to be in the correct location.
This is the exact error message:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /Views/Product/Index.cshtml
The thing is, there is an Index.cshtml in /Views/Product.
This is an excerpt from my Global.asax:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Product", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Resolution:
The issue was that I had set Index.cshtml as the "start page" by right clicking and choosing "Set as Start Page". After typing the URL manually per nemesv's suggestion and seeing that everything worked, I went into the project settings and changed Web->Start Action to "Current Page".
You requested for /Views/Product/Index.cshtml, which is wrong. You must create a ProductController class with an Index method:
public class ProductController : Controller
{
public void Index()
{
return View();
}
}
and then request for localhost:yourport/ to get the index for product controller as you defined it in your routes (or just /Product/Index).

Correct 404 message for a missing ASP.Net MVC controller

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' Application..."
I have a base controller that has a NotFound action that will render the nice 404 page.
Missing actions are handled:
protected override void HandleUnknownAction(string actionName)
{
this.NotFound(actionName).ExecuteResult(this.ControllerContext);
}
So a visit to {site}/ValidController/NotAnAction gets routed correctly.
However a visit to {site}/NotAController doesn't.
I have routes set up with a catch all:
routes.MapRoute(
"MVC routes",
"{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
"Catch All",
"{*url}",
new { controller = "System", action = "NotFound" });
The catch all correctly catches routes that don't match.
So {site}/Invalid/Action/id/extra is correctly routed via the catch all.
However {site}/Invalid gets picked up via the "MVC routes" route and ASP.Net goes looking for InvalidController, and throws a dumb exception when it doesn't find it.
I know that I can override this at the web.config level, but that just redirects to the page. I'd like to know when the route pattern matches but the controller is not a valid controller name.
Where can I catch and change this behaviour?
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.
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?

MVC Handle 404s and set default error page

I'm tying to enable the default routing in MVC.
I want every 404 request to redirect to DefaultController DefaultRout()
I found How can i make a catch all route to handle '404 page not found' queries for ASP.NET MVC?
But {*url} dosen't work i'm getting 404 and not redirecting to the default page.
My code:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{resource}.aspx/{*pathInfo}");
routes.IgnoreRoute("{resource}.ascx/{*pathInfo}");
routes.IgnoreRoute("{resource}.ashx/{*pathInfo}");
routes.IgnoreRoute("{resource}.gif/{*pathInfo}");
//http://localhost:4775/BW/A/Tasks
routes.MapRoute("Pages", "A/{controller}", new { controller = "Tasks", action = "InitPage" });
routes.MapRoute(
"404-PageNotFound",
"{*url}",
new { controller = "Default", action = "DefaultRout" }
);
}
What am I missing?
Thanks
Rafael
Are you unable to use your web.config? I think this would be easier:
<customErrors mode="On" defaultRedirect="/error/default">
<error statusCode="403" redirect="/error/restricted"/>
<error statusCode="404" redirect="/Default/DefaultRoute"/>
<error statusCode="500" redirect="/error/problem"/>
</customErrors>
There are a few things that I would like to point here.
I notice that you have used many IgnoreRoute entries for physical files. You don't have to do that as the framework looks for the physical files matching the url by default before routing it. You can disable the physical file matching by turning RouteExistingFiles to true on the RouteCollection in Global.asax. In this case you haven't done that.
Secondly, the way you have set it up, any route but /A/{controller} will be caught by the catch all route (anything starting with * is a catch all route) that you have configured.
I have tried this configuration and it does catch all the other routes except the one mentioned above. One thing you have to keep in mind, however, is that the above configuration will still matching everything with the following type of url: /A/something/ because the second segment will always match the {controller} placeholder. To only match this url with the "Tasks" controller, you can define a constraint on the route as the following:
routes.MapRoute("Pages", "A/{controller}", new { controller = "Tasks", action = "InitPage" }, new {controller="Home"});
There is also a spelling mistake in your catch all route configuration. action = "DefaultRout" should be action = "DefaultRoute"
Hope this helps.

Resources