Keep getting 404 eventhough I mapped my routes - c#

I have been dealing with some issues about routes. I have defined the routes but I keep getting 404. Here are the routes :
routes.MapRoute(
name: "Default",
url: "{controller}",
defaults: new { controller = "Login", action = "Login" }
);
routes.MapRoute(
name: "Home",
url: "{controller}/{Date}",
defaults: new { controller = "Home", action = "Home", Date = UrlParameter.Optional }
);
routes.MapRoute(
name: "Calendar",
url: "{controller}/{action}",
defaults: new { controller = "Calendar", action = "Index" }
);
routes.MapRoute(
name: "Act",
url: "{controller}",
defaults: new { controller = "Act", action = "New" }
);
localhost:51081/login works!
localhost:51081/Home/25.04.2013 works!
localhost:51081/act doesnt work!
localhost:51081/calendar/index doesnt work!
Here "login" and "home" works but "calendar" and "act" doesnt. When I move "calendar" mapping to the top then "home" mapping doesnt work. how do you map your pages?
Basically I dont want action name to appear on the url ex : http://localhost:51081/Home/Home/25.04.2013. I want to see it like http://localhost:51081/Home/25.04.2013 or http://localhost:51081/calendar

Like #MarcGravell says: you only add special rules for the exceptions
In your case routes Calendar and Home are the same.
You can map your routes more specific by replacing {controller} with the Home, cause that route isn't that dynamic and is really an exception(it ignores the action)
routes.MapRoute(
name: "Home",
url: "Home/{Date}",
defaults: new { controller = "Home", action = "Home", Date = UrlParameter.Optional }
);
Act is the same as calendar so you don't need two routes for those. Just call Act/New instead of only Act.
For the Default use:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Login", action = "Login" }
);
And put it at the bottom of your routes off course.

routes.MapRoute(
name: "Default",
url: "{controller}",
defaults: new { controller = "Login", action = "Login" }
);
This defines a route that matches / and /anything; the / will try to use LoginController.Login, and /anything will try to use anythingController.Login. Note that at no point does this route allow it to pick up any "action" other than Login.
If you trow all of those away, and use something like:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}",
defaults: new { controller = "Home", action = "Index" }
);
then that will match any of /, /anything and /anything/anythingelse.
/ will map to HomeController.Index
/anything will map to anythingController.Index
/anything/anythingelse will map to anythingController.anythingelse
Hopefully that explains how the mapping works in terms of defaults.
If you have any specific routes, they should be added before this blanket default.

Remember that asp.net routes are evaluated in the order in which you add them to the MapRoute table.
Your "default" and "act" routes are the same, since they have the same pattern. "Act" will probably never get hit.
Also, your "default" route is pretty generic, and most requests will satisfy it. You should add your routes in order of most specific (e.g. hard-coded routes) to least specific (e.g. all placeholders).
So if I have a request of foo/bar, it will fall to your "default" route since "foo" will be interpreted as the controller -- it's then going to look for a resource of "bar" which probably doesn't exist. So you'll get a 404.
Your "home" and "calendar" routes are also the same pattern, so only one will get hit (which will be the first defined).
Make your routes more specific, and define them from most specific to least.
Good luck!

Related

MVC RouteConfig affected the Html.ActionLink() helper method

In my MVC project, I've Item controller and some actions like Index.
The RouteConfig includes:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
In some views, I'm using the helper method Html.ActionLink("Items","Index","Item") to create anchors for the index action. So the href of the anchor result will be (/Item/Index)
Now, I need to map the following static URL:
/IndirectItem/Index
to the Index action of the Item controller with default parameter (indirect = true), so the RouteConfig will be:
routes.MapRoute(
name: "IndirectItem",
url: "IndirectItem/Index",
defaults: new { controller = "Item", action = "Index", indirect = true }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
It seems OK and the client requests were mapped correctly , but all anchors resulted from the Html.ActionLink("Items","Index","Item") helper method were mapped to the URL (/IndirectItem/Index) instead of (/Item/Index).
How can I fix this problem without changing all Html.ActionLink() to Html.RouteLink() or adding another route for the original url ?
Using constraints will be a convenient solution for your problem.
Use the following IndirectItem route instead of yours.
routes.MapRoute(
name: "IndirectItem",
url: "{staticVar}/{action}",
defaults: new { controller = "Item", action = "Index", indirect = true},
constraints: new { staticVar = "IndirectItem" }
);
and you don't need any change in the Default route.
It works fine with me.
You are experiencing this issue because Html.ActionLink uses Routing table for generating URLs and since IndirectItem route is match to Html.ActionLink("Items","Index","Item") (because it has Index action and Item controller specified in both route and action link). The resolving done by a first match so the order of routes registration matters
By adding DefaultItem route:
routes.MapRoute(
name: "DefaultItem",
url: "Item/Index/{id}",
defaults: new { controller = "Item", action = "Index", id = UrlParameter.Optional }
);
prior to your current routes:
routes.MapRoute(
name: "IndirectItem",
url: "IndirectItem/Index/,
defaults: new { controller = "Item", action = "Index", indirect = true}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Should fix the issue
Another option could be creating empty IndirectItem controller that inherits from Item controller:
public IndirectItemController : ItemController
{
}
and then changing the route to
routes.MapRoute(
name: "IndirectItem",
url: "IndirectItem/Index/,
defaults: new { controller = "IndirectItem", action = "Index", indirect = true}
);
The answers given by Omar Gohar and Alex Art are misleading.
The problem you are running into is that your route does not match when generating the URL. This is simply because you have not provided all of the route values to create a match in your ActionLink.
#Html.ActionLink("Items", "Index", "Item", new { indirect = true }, null)
If changing your ActionLink declaration is not an option, you can attach your "indirect" metadata to the route using the DataTokens parameter.
You use the DataTokens property to retrieve or assign values associated with the route that are not used to determine whether a route matches a URL pattern. These values are passed to the route handler, where they can be used for processing the request.
routes.MapRoute(
name: "IndirectItem",
url: "IndirectItem/Index",
defaults: new { controller = "Item", action = "Index" }
).DataTokens = new RouteValueDictionary(new { indirect = true });
The bottom line is that RouteValues (which are populated by the defaults if not provided by the URL pattern) are not meant to be used for metadata. They are meant to be real data to match to make the URL unique.
Of course, if you are not actually using the indirect route value for anything, you can simply omit it from the route.
routes.MapRoute(
name: "IndirectItem",
url: "IndirectItem/Index",
defaults: new { controller = "Item", action = "Index" }
);

ASP.NET MVC service route overrides default route

I have added WCF service to MVC 5 application, and created a route for it:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new ServiceRoute("Service1.svc", new ServiceHostFactory(), typeof(Service1)));
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
The problem is that all my links leads to the Service1.svc route now. #Html.ActionLink("Passport Maker", "Index", "Home", new { area = "" }, new { #class = "navbar-brand" }) become http://localhost:50099/Service1.svc?action=Index&controller=Home and other links change in the same way.
If I add ServiceRoute after "Default" route, the links work correctly but the service is unavailable.
Why it happens (there is no "Service1" in the links, why they select the service route then?) and how to fix it?
The solution:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { controller = "^(?!Service1.svc).*" }
);
routes.Add(new ServiceRoute("Service1.svc", new ServiceHostFactory(), typeof(Service1)));
Explanations for those who may encounter a similar problem: the reason of the problem was that Html.ActionLink uses the first matching route to generate the link. And my service route was the first and was matching, because a route does not require to include {controller} and {action} parameters to be matched (as I thought initially).
The solution is to put the default route first, so it is used by Html.ActionLink. And to still be able to use the service route, need to exclude it from the the first route using constraints. The regex ^(?!Service1.svc).* matches only those controller names that don't start from "Service1.svc".

MVC 4 custom routing str and int

I need to have a custom route, like the default one, but this one should accept numeric values as strings. Like 0015. If I leave the parameter type as int, the value passed to the controller method get truncated to 15. And I need 0015.
So what I did, I created the following:
routes.MapRoute(
name: "AccRef",
url: "{controller}/{action}/{acc_ref}",
defaults: new { controller = "Company", action = "Index", acc_ref = "" },
constraints: new { acc_ref = #"^\d{1,4}$" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
And the problem is, as I understand, that when I now pass in an integer as "id"
#Url.Action("Method", "Controller", new { id = item.ref})
from the view, the routing still applies the first route to it and the call fails.
How would you go about solving this problem with routing?
Is it possible to have two same routing configurations where one accepts int and another string?
Your AccRef is too greedy.
If you look at the url generated from the Url helper it is:
Controller/Method/id
This matches your first AccRef route as well as the default route.
You have to be more specific with your routes. Also the order you define your routes are important. So you normally want to define greedier routes last.
Phil Haack has a route debugger on nuget (blog post here) which can help you identify route issues.
If you reverse the order like so:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "AccRef",
url: "{controller}/{action}/{acc_ref}",
defaults: new { controller = "Company", action = "Index", acc_ref = "" },
constraints: new { acc_ref = #"^\d{1,4}$" }
);
Your current scenario will work with above change but the url "/Home/Index/5" or "/Company/Index/0015" still matches both the routes. This is because the routes are generic (as correctly pointed out by Bigfellahull).
In your case since both the parameter is of type int, both the routes are matched.
Option 1:
You can add a extra string say "Acc" in the route url to make it more specific.
routes.MapRoute(
name: "AccRef",
url: "{controller}/{action}/{acc}/{acc_ref}",
defaults: new { controller = "Company", action = "Index", acc_ref = "" },
constraints: new { acc_ref = #"^\d{1,4}$" }
);
In this case the url will change to ".Company/Index/acc/0015".
Option 2:
If you can change the parameter type in action method like so:
public class HomeController : Controller
{
public ActionResult Index(string id)
{
}
}
The url will match only one route.
Option 1 and 2 are for example only to explain how you can make routes more specific.

ASP.NET MVC 4: keeping Index as defaul view

By default MVC 4 application has the following routes in ProjectFolder\App_Start\RoutConfig.cs:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
In browser, any call to http://host:port/controller/ would take you to http://host:port/controller/Index.
Now, if you want to change the start page, lets say SomeController\MyAction, you can change it like:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "SomeController", action = "MyAction", id = UrlParameter.Optional }
);
But then http://host:port/controller/ won't display http://host:port/controller/Index but the 404 error page. You would need to explicitly append /Index in URL.
If you change the startup page from project properties and keep the routes to Home\Index, then everything would work fine except the URL root http://host:port/ won't display the startup page. When you run the application it will append the startup page name in the URL. But if you have relative hyperlink like Home somewhere in your application, it would not take you to the desired homepage
Is there a way to configure the startup page "SomeController\MyAction" such that; root URL always point to it and the Index be the default view of every controller?
Something equivalent to Ruby on Rails' route:
root :to => "some_controller#my_action"
which keeps the default view binding to index action.
You need to add extra route just for Site root, before default route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(name: "SiteRoot", url: "",
defaults: new { controller = "SiteRoot", action = "Main" });
routes.MapRoute(name:"Default", url:"{controller}/{action}/{id}",
defaults:new {controller = "Home", action = "Index", id = UrlParameter.Optional});
}
Now "/" will take you to SiteRoot/Main, while "/Home" will take you to Home/Index.
Note that in this case "/SiteRoot" will also take you to "SiteRoot/Index", not to "SiteRoot/Main". This can be easily changed with additional route specific for url: "/SiteRoot".
Additionally, if you want to be extra precise, in default route you can remove part controller = "Home", since 1st route now covers this case. However, leaving default route as it is will also work.
routes.MapRoute(name:"Default", url:"{controller}/{action}/{id}",
defaults:new { action = "Index", id = UrlParameter.Optional});
It sounds like you want this:
routes.MapRoute(
name: "SomeController Default",
url: "SomeController/{action}/{id}",
defaults: new { action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
This is the how the default routing for ASP.NET Web API works, like this:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

Optional parameter in .net route beginning

there's
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
I want to make my url to allow enter company name in the beginning, e.g.:
url: "{company}/{controller}/{action}/{id}"
So I could navigate through pages and now that the base is some company.
domain.com/company-ltd
domain.com/company-ltd/products
domain.com/company-ltd/edit
domain.com/some-other-company-name-ltd/products
and so on.
How could I achieve this? Thanks.
The routes work off the back of patterns; a URL coming in is matched against patterns until one is found that it matches.
Assuming that you're not talking about optional company names, you should be able to get away with:
routes.MapRoute(
name: "Default With company",
url: "{company}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional
);
and then simply ensure that all methods take a company parameter. However, this would fail on more simple routes, so make sure you set a default for pages such as the root. If you do end up wanting additional routes that don't involve a company, those can also be catered for with controller-specific routes:
routes.MapRoute(
name: "Profile",
url: "profile/{action}/{id}",
defaults: new { controller = "Profile", action = "Index", id = UrlParameter.Optional
);
before the less-specific company one.
Note that you can test these routes quite easily in .net using NUnit and MvcContrib.TestHelper, for example:
"~/company/".WithMethod(HttpVerbs.Get)
.ShouldMapTo<Controller.HomeController>(x =>
x.Index("company"));
"~/company/products/".WithMethod(HttpVerbs.Get)
.ShouldMapTo<Controller.ProductsController>(x =>
x.Index("company"));
to ensure that they're going to the locations you'd expect.

Categories