Why is this MVC route not working? - asp.net-mvc

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)

Related

MVC Route for a Single Controller to handle all Actions [duplicate]

I'm looking to do something similar to this post:
How to hide controller name in Url?
only without any sort of ID.
The server is running IIS 6 and the pages already show up without extensions so it's not a wildcard issue.
I'm looking to hit http://website.com/action-name
I have http://website.com/controller/action-name working
I'm assuming this is just a simple routing change that I am somehow goofing up. My current routing rule is:
routes.MapRoute(
"RouteName",
"{action}",
new { controller = "Home", action = "Index" }
);
Is your new routing rule positioned above the default routing rule of {controller, action, id} so that it has the opportunity to match first?
The problem is your default route is still probably in place so it is matching it first and defaulting the rest of the inputs it expects. Based on your comment that the controller/action is working makes me think you didn't remove it or it is appearing first. Can you post your entire RegisterRoutes?
Try making the route you defined the very first route and it should match almost anything you pass at it.
EDIT: Added what your RegisterRoutes should look like:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// This will match anything so if you have something very specific with hard coded
// values or more items that will need to be match add them here above but do not
// add defaulted values so it can still fall through to this.
routes.MapRoute(
"RouteName",
"{action}",
new { controller = "Home", action = "Index" });
}

Setting up ASP.Net MVC 4 Routing with custom segment variables

I just began working on an application with a couple areas (basic grid master / details type system..) I'm looking into taking advantage of the nice routing features in MVC (4 specifically) and I'm "just not getting it" I presume.
Currently the only route defined is the basic one:
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Account", action = "Index", id = UrlParameter.Optional }
);
which is fine, it works with the areas we have defined, so I'm assuming it must know where the user is and route to the appropriate controller based contextually on the location / area that the user is in.. so far nice..
Now, i'm trying to set up a new route that can handle
/someController/someAction/{statusName}
and specifically something like:
/OrderManager/List/New
AND
/OrderManager/List/Viewed
where "New" is the "New" status and have the Action Signature look like:
public ActionResult List(string statusName)
i was assuming I could just add the new route below the default one identifying "statusName" instead of Id, but of course, how the H would the routing mechanism know the difference between :
/controller1/action1/15
/controller2/action2/new
I did try adding a "static" route in the form of
routes.MapRoute("Default",
"ControllerName/ControllerAction/{statusName}",
new { statusName = UrlParameter.Optional }
);
I thought I could "hiJack" just that one route and do something special with it, but to know avail, the router stops at first match?? I'm assuming that was the wrong way to address this issue anyhow..
so now I'm going through the idea of getting to something like:
/somecustomroutename/somesortValue
ex.
/OrderManagerList/viewNew
where these routes would basically be "aliases". I had thought that adding the following route would do the trick:
routes.MapRoute("Default_List",
"OrderManagerList/{statusName}",
new {controller="OrderManager", action="List", statusName= UrlParameter.Optional }
);
with the associated action on the OrderManager controller:
public ActionResult List(string statusName)
no matter what I try, the argument is null, or the "resource cannot be found"
I know the controllers need to have a corresponding View file.. but that's not the issue here, the issue is my attempt at understanding the routing..
SO my questions.. fundamentally, what am I missing about the routing in MVC (4)? even some good articles for a simpleton like myself to understand?
my understanding; define a route, and map it's "endpoint".. however, i think i'm not understanding the assumptions that the machine is making..
anyhow, let me know if further explain / edit is required..
thanks in advance.
The basic principle of routes is that they are evaluated from the top down, and the routing systems uses the first match, not the best match.
The implication of this is that you must order your routes in order of specificity, with the most specific route first and the most general route last.
With this principle in mind, let’s look at your situation. Initially, you have only the default route defined:
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Account", action = "Index", id = UrlParameter.Optional }
);
The URL pattern is "{controller}/{action}/{id}". By itself, this would match any three-segment URL, and would not match any URL that had less than three segments. However, the third input parameter is the default parameter, which defines defaults for the first and second segment and indicates that the third segment is optional. The next effect of this is to make the route match URL having 0,1, 2 or 3 segments.
Now you want to add a route where the third URL segment is mapped to a “statusName” parameter, and can handle URLs like:
OrderManager/List/New
OrderManager/List/Viewed
There are two basic approaches you can take here. You can 1) create a very specific route that will handle these two URLs only, or 2) you can try and create a more general route to handle the general case. Let’s look at the first case first. You can create a route as follows:
routes.MapRoute("", "OrderManager/List/{statusName}",
new { Controller = "OrderManager", Action = "List" });
Note that because this route is more specific than the default route, you must put this route before the default route.
If you want to have a more general route, you need to decide how this route will differ from the default route, since they both will match URLs having three segments. Let’s say you decide that the new route will accept anything that only contains letters in the third segment, leaving the default route to handle anything containing numbers. You can do this using route constraints. For example you could write a route as follows:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { id = #"^\d+$" });
routes.MapRoute(
name: "NewRoute",
url: "{controller}/{action}/{statusName}",
defaults: new { Controller = "OrderManager", Action = "List" },
constraints: new { statusName = "^[A-Za-z]+$" });
With these two routes, any three-segment URL having only letters in the third segment will put the third segment into a variable called “statusName”, whereas any URL with an integer in the third segment will put the third segment into a variable called “id”.
In any applications of real complexity, routes can get complicated, and it is very advantageous to write unit tests for your routes, to insure that you don’t screw things up when you add or modify a route.
For good references on routing, see Scott Sanderson's book or see the MSDN documentation

How do I use constraints in ASP.net MVC 4 RouteConfig.cs?

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.

MVC Routing: Trying to get dynamic root value working

I am trying to define dynamic sections of my site with the root url of the site. I am having some trouble defining the right MVC Route for it. Can someone please help.
My desired url will look like this: http://website.com/[dynamic-string]
But I have other standard pages like: http://website.com/about or http://website.com/faq or even just http://website.com.
My routes don't work correctly with that dynamic string. As shown below.
This is the route for the dynamic-string.
routes.MapRoute(
"CommunityName", // Route name
"{communityName}", // URL with parameters
new { controller = "Community", action = "Community", communityName = UrlParameter.Optional }
);
This is the route for all other STANDARD PAGES
routes.MapRoute(
"Default", // Route name
"{action}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
My routes just don't match up. Everything either gets diverted to one or the other route depending on which route is declared first.
There is no difference between the two routes you mention. How can MVC know which url should be mapped to communityName and which to action? Any url can match both.
You can define your standard pages as a route (before the CommunityName route) or you can catch them in your Community action, see if the name matches a function in your Home controller and then call the right action function.
I've never done this before but you might be able to create a more intelligent routehandler that looks at your controller actions, checks if the action really exists and if true selects that route.
this is beacuse the routes are effectively the same. When you declare the action route you do not state any constraints to the route, for this reason anything will be assumed to be a the action name.
If you want two routes to capture at the same level then you must constrain the action names to those that exist on your controller, this way if it does not match it will pass to the next route.
You can see an example of and advanced constraint here:
http://blogs.planetcloud.co.uk/mygreatdiscovery/post/Custom-route-constraint-to-validate-against-a-list.aspx

ASP.NET MVC routing conflict - null value for input variable

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

Resources