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

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

Related

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.

ASP.NET MVC 3 Routes Always Have Querystring Value "Area="

This is an annoyance that I've experienced for a long time, but now my client is asking me to address it.
In every route that gets generated (by a non-Default route), a query string value gets appended: "Area="
As an example:
// RouteConfig.Register():
routes.MapRoute(
"ProfileDetails",
"{slug}",
new { controller = "Profile", action = "Details" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
To generate a URL to the BadgeController.Index action, the Default route will be applied and the result will be /Badge... and that's what is expected.
But to generate a URL to the ProfileController.Details(someUser) action, the ProfileDetails route will be applied and the result will be /someUser?Area= ... which will work, but the ?Area= is unnecessary and messy.
I have no areas in my project. How do I get rid of that Area= query string value? This happens with all of my routes that are not the predefined Default route, not just the "ProfileDetails" one in this example.
I've tried removing the AreaRegistration.RegisterAllAreas() from my Global.asax file, since I assume it's not required.

How to modify MVC routing to get more than one first-class urls

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.

inbound/outbound url routing in asp.net MVC

The routing I need is quite simple, I must be missing something there. As code example I put the simpler situation where I can reproduce my behavior.
You have this ActionMethod :
public ActionResult Index(string provider)
{
ViewData["Message"] = provider;
return View("Index");
}
And you have this route :
routes.MapRoute(
null,
"{controller}/{action}/{provider}",
new { controller = "Home", action = "Index", provider = "Default" }
); // Parameter defaults
You can call /Home/Index/Custom and provider will take the value "Custom"
What Route would I need if I want the url /?provider=Custom to map the provider to the parameter.
I thought that would just work, because the default controller and the default action would be used, and the provider from the querystring would be used instead of the default one.
but the querystring is just ignored here.
That's a problem in my situation as I have a form using HTTP GET method.
The form action has to be Html.BeginForm(c=>c.Index(null)) which is resolved as / and the value of my form are added in the querystring. (the provider being a dropdown in the form)
So the url built by the form is /?abc=value&cde=value...
UPDATE
The accepted answer below (see the comments) led me to this solution:
routes.MapRoute(
"Search",
"search/",
new { controller = "Home", action = "Index" }
);
routes.MapRoute(
null,
"{controller}/{action}/{provider}",
new { controller = "Home", action = "Index", provider = "Default"}
);
And declare the form like so :
Html.BeginRouteForm("Search", FormMethod.Get){
...
}
This way, the form will work with the provider in the QueryString (when I use the named route search) but in all other case, I will use the default route. :)
When I set the provider to urlparameter.optional instead of a static value, I get the behavior that you are looking for. I don't think I can explain fully why this works whereas having a static default value set does not, but give it a try and see if it helps. If it works, you may also want to develop a custom route for your form so you can maintain your default provider in your routes as opposed to doing custom checking in your controllers.
routes.MapRoute( _
"Default", _
"{controller}/{action}/{provider}", _
New With {.controller = "Home", .action = "Index", .provider = UrlParameter.Optional} _
)
UPDATE:
Also, you do not have to have the parameters in your route to pass them to a controller action method. For instance, using the route above, I can have this URL
http://localhost:49705/home/about/default?otherValue=testme
And this controller method
Function About(ByVal provider As String, ByVal otherValue As String) As ActionResult
ViewData("Message") = provider & "|" & otherValue
Return View()
End Function
Which outputs the string default|testme
This URL does the same as above: http://localhost:49705/home/about/?provider=default&otherValue=testme
Maybe I'm not understanding the question, but if you just remove the {provider} from your route, or use the default {id} instead. Then when you set the URL to /?provider=blah, "blah" is assigned to the "provider" parameter.

Why is this MVC route not working?

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)

Resources