Here are the URL's that I'm trying to map with MVC3
routes.MapRoute( "Products", "{controller}/{id}/{*name}", new { action = "view" }, new { id = #"\d+" } );
/products/13/seo-friendly-name-of-the-product
Now the next route I need to map is this
routes.MapRoute( "General", "{controller}/{id}/{action}", new { }, new { id = #"\d+" } );
/user/42/changepassword
I want to know how to resolve this problem. Simply changing the order isn't enough because one area of the app stops working. I know that {*name} and {action} are being conflicted, but I don't know what to do to fix this.
The goal is to eliminate ambiguous matches, so let's look at making one more specific. Is the controller for your Products route always "Products"? If so, could you change that route to
routes.MapRoute(
"Products", // Route name
"products/{id}/{*name}", // URL with parameters
new { action = "view", controller="Products" }, // defaults
new { id = #"\d+" } // constraints
);
This conflict is difficult to resolve simply because:
/products/13/seo-friendly-name-of-the-product
/user/42/changepassword
are the same and it will always be the first route that will pick the request. You might need to put some constraints (like for example saying that the catch-all value will always contain dashes which would disambiguate it from the other route because an action name cannot contain dashes or fix the controller for the catch-all route).
Related
I'm trying to get some routing constraints working using the latest asp.net mvc 4 architecture. Under App_Start there is a file called RouteConfig.cs.
If I remove the constraints section from my example below, the url works. But I need to add some constraints so that the url doesnt match on everything.
Should work: /videos/rating/1
Shold NOT work: /videos/2458/Text-Goes-Here
This is what I have:
//URL: /videos/rating/1
routes.MapRoute(
name: "Videos",
url: "videos/{Sort}/{Page}",
defaults: new { controller = "VideoList", action = "Index", Sort = UrlParameter.Optional, Page = UrlParameter.Optional },
constraints: new { Sort = #"[a-zA-Z]", Page = #"\d+"}
);
If you want multiple optional parameters on the same route, you will run into trouble because your urls must always specify the first one in order to use the second one. Just because you use constraints doesn't stop it from evaluating the parameters, it instead fails to match this route.
Take this for example: /videos/3
When this is trying to match, it finds videos, and says, "OK, I still match". Then it looks at the next parameter, which is Sort and it gets the value 3, then checks it against the constraint. The constraint fails, and so it says "OPPS, I don't match this route", and it moves on to the next route. In order to specify the page without the sort parameter defined, you should instead define 2 routes.
//URL: /videos/rating/1
routes.MapRoute(
name: "Videos",
url: "videos/{Sort}/{Page}",
defaults: new { controller = "VideoList", action = "Index", Page = UrlParameter.Optional },
constraints: new { Sort = #"[a-zA-Z]+", Page = #"\d+"}
);
//URL: /videos/1
routes.MapRoute(
name: "Videos",
url: "videos/{Page}",
defaults: new { controller = "VideoList", action = "Index", Sort = "the actual default sort value", Page = UrlParameter.Optional },
constraints: new { Page = #"\d+"}
);
I put the most specific routes first when possible and end with the least specific, but in this case the order should not matter because of the constraints. What I mean by specific is most defined values, so in this case you must define the sort in the first route, and you also can specify the page, so it is more specific than the route with just the page parameter.
My input maybe rather late, but for others still searching for answers.To keep things simple, i would use the following in my RoutesConfig file
routes.MapRoute(
name: "Videos",
url: "{controller}/{action}/{id}",
defaults: new { controller = "VideoList", action = "Index", id="" },
constraints: new { id = #"\d+"}
);
Depending on your choice of implementation, id could be UriParameter.Optional, but in this scenario it is going to be id="" ,because we will be passing a string/int during runtime.
This style was adopted from http://www.asp.net/mvc/overview/older-versions-1/controllers-and-routing/creating-a-route-constraint-cs
One thing to keep in mind by convention controller classes always end with controller e.g VideoListController class. This class should be listed under the controller folder containing the following method
public ActionResult Index(string id)
{
// note this maps to the action
// random implementation
ViewBag.Message=id;
View()
}
// note this approach still matches any string...
To match only integers, the Index method has to be rewritten
public ActionResult Index(int id)
{
// note this maps to the action
ViewBag.Message=id;
View()
}
Consequently, this approach works for VideoList/Index/12
but upon putting VideoList/Index/somerandomtext it throws an error during runtime. This could be solved by employing error pages.
I hope this helps. Vote if its quite useful.
I have default routing set for my mvc application like:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
I have Home controller with Index() and About(). Tell me please how to modify routing to get both domain.com/Index and domain.com/About urls?
Thank you
Add this before your default route. By adding it before the default route, if it matches it will be used to set the RouteDictionary values. Untested, but it should map urls that only have a single component that is either index or about. Note, that this assumes you don't have an index or about controller. The routing constraint is important as it keeps it from matching on each controller's index action, e.g., controller/.
routes.MapRoute(
"IndexOrAbout",
"{action}",
new { controller = "home", action = "index", id = "" },
new
{
action = "(index)|(about)"
}
);
Note, if you need to expand this to more top-level routes or make it more dynamic you could use a custom routing constraint that could draw the top-level values from a database or configuration. At that point, you'd probably want to change it from using the action parameter to the id parameter and have a single action that use the id to determine what to show rather than have an action per value.
I have tried routes.mapRoute but i can't figure a way in MVC 3 to use it make a root path route to an action. e.g mywebsite.com/party should redirect to mywebsite.com/events/party where events is the controller and party is the action.
Is this even possible?
Without seeing your existing routes, its hard to give you an exact solution.
One rule to keep in mind:
MVC will resolve the first in your route collection that matches the requested URL
not necessarily the most specific match.
Make sure you do not have another rule that would also satisfy that route placed earlier in your code, e.g. the routing algorithm might be finding a "party" controller and "index" action because you have a default rule like:
routes.MapRoute(
"Default",
"{action}",
new { controller = "Home", action = "Index" }
);
placed before your rule.
You need to put something like
routes.MapRoute(
"PartyRoute",
"party",
new { controller = "Events", action = "Party" }
);
BEFORE any route that might match a URL with just a single parameter
routes.MapRoute(
"Default",
"{action}",
new { controller = "Events", action = "Index" }
);
I'm at a loss as to why my routes are conflicting. I have these in my Global.asax file:
routes.MapRoute(
"CustomerView", "{controller}/{action}/{username}",
new { controller = "Home", action = "Index", username = "" }
);
routes.MapRoute(
"Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "0" }
);
So far everything has worked fine except when I created a controller action like so:
public ActionResult MyAction(int id)
{
//Do stuff here
return View();
}
When I try viewing it through http://mydomain/MyController/MyAction/5 I get:
Server Error in '/' Application.
The parameters dictionary contains a
null entry for parameter 'id' of
non-nullable type 'System.Int32' for
method 'System.Web.Mvc.ActionResult
Track(Int32)' in
'InTouch.Controllers.OrderController'.
To make a parameter optional its type
should be either a reference type or a
Nullable type. Parameter name:
parameters
suggesting to me that the id value is not being read properly. Sure enoguh, when I swap the order of the routes around it works fine. My (admittedly limited) understanding so far was that, if a variable name specified in a route matches that specified in a controller action definition, it will assume that one regardless of order. Apparently I was wrong. Swapping the order causes other controller actions to break. What is the right way to handle my routes in this instance?
The problem with your example is that the match is happening on the first route and it's seeing "5" as the username parameter. You can use constraints to limit what values are accepted for each parameter to accomplish what you're wanting. Since the "Default" route that accepts an Id is more restrictive than the "CustomerView" route, I would list the "Default" route first with a constraint on the Id parameter:
routes.MapRoute(
"Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "0" },
new { id = #"\d+" }
);
This will cause the first route to only match if Id is an integer value. All other requests would then fall through to the "CustomerView" route that would pick up any other requests that didn't have integers as that third parameter.
Check out Creating a Route Constraint for an explanation of constraints.
My (admittedly limited) understanding
so far was that, if a variable name
specified in a route matches that
specified in a controller action
definition, it will assume that one
regardless of order.
The binding of route values to action arguments happens AFTER the framework determines which route to use. Route selection is performed using a "first match wins" heuristic: the first route that can successfully match the incoming request is used, even if a "better" route was defined later.
Michael's solution is correct. You need to list the Default route first, using route constraints to only match URLs where the ID is numeric. Your second, less restrictive route should come next.
NOTE: If you follow Michael's solution you'll run into problems if you have any users with a username consisting only of numbers. You might consider adding some other discriminating factor to the routes, like putting the keyword "user" in the 2nd one:
routes.MapRoute(
"Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "0" },
new { id = #"\d+" }
);
routes.MapRoute(
"CustomerView", "{controller}/{action}/user/{username}",
new { controller = "Home", action = "Index", username = "" }
);
here are two routes from my global.asax file. I'm trying to go to the second route and I'm getting a default 404 resource not found error.
When i remove the first route (listed in this example), it works.
How can i fix this, please?
Snippet of global.asax code
// GET: /user/PureKrome/Alert/69
routes.MapRoute(
"User-Alert-Details",
"user/{displayName}/alert/{alertId}",
new { controller = "Account", action = "AlertDetails", alertId = 0 });
// GET: /user/PureKrome/Alert/create
routes.MapRoute(
"User-Alert-Create",
"user/{displayName}/alert/create",
new { controller = "Account", action = "AlertCreate" });
Your first route is a "greedy" route and will happily accept "create" as the alertId in the last parameter. It appears that you intend the alertId parameter to be numeric only, so you should add a constraint to tell the route system that that last parameter must be numeric.
See this tutorial.
For example:
// GET: /user/PureKrome/Alert/69
routes.MapRoute(
"User-Alert-Details",
"user/{displayName}/alert/{alertId}",
new { controller = "Account", action = "AlertDetails", alertId = 0 },
new { alertId = #"\d+" });
// GET: /user/PureKrome/Alert/create
routes.MapRoute(
"User-Alert-Create",
"user/{displayName}/alert/create",
new { controller = "Account", action = "AlertCreate" });
Note, you can also reverse the order of the routes, but even if you do, you should still include a constraint for correctness if you want alertId to always be a number.
You want the routes to be defined the other way round so that the exact match on create comes before the unconstrained match for alertId. That, or you can add a constraint to alertId as stated by Twisty Maze.
This is because the routing works by trying to match the routes from top to bottom. /user/PureKrome/Alert/create matches on the User-Alert-Details route as it thinks create is the value for alertId. By switching them around it will only match the User-Alert-Create route if the 4th segment is explicitly create and it will fall through to User-Alert-Details if it doesn't.
For clarity, they should work this way around:
// GET: /user/PureKrome/Alert/create
routes.MapRoute(
"User-Alert-Create",
"user/{displayName}/alert/create",
new { controller = "Account", action = "AlertCreate" });
// GET: /user/PureKrome/Alert/69
routes.MapRoute(
"User-Alert-Details",
"user/{displayName}/alert/{alertId}",
new { controller = "Account", action = "AlertDetails", alertId = 0 });
If you have another issue like this, try Phil Haack's url debugger at http://haacked.com/archive/2008/03/13/url-routing-debugger.aspx
The problem is that you have specified Default Values for controller and actions in your first Mapping.
Now any incoming request is handled by the first route, if the controller name is missing, its replaced by the default value and if the action name is missing that is also replaced by the default value.
So in reality when you say http://localhost/SomeRoute
The first mapper comes into action and considers the string "SomeRoute" as a Controller name, then it does not find the action so it uses the default action you specified which is "AlertCreate" in your example.
So now the mapper tries to find a Action called AlertCreate in the "SomeRoute" controller.
Bottom line is the second mapping does not kick into action because the first mapping is handling all your routing request. (because you have default values specified)