Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 614
nathanchere Avatar asked Feb 09 '10 04:02

nathanchere


2 Answers

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.

like image 103
Michael Paladino Avatar answered Nov 15 '22 05:11

Michael Paladino


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 = "" }
);
like image 28
Seth Petry-Johnson Avatar answered Nov 15 '22 06:11

Seth Petry-Johnson