Search code examples
asp.net-mvcasp.net-mvc-routing

MVC routing - Query strings are not applied when default is present


I'm using this route config :

   routes.MapRoute("Default23",
                "{category}",
                new { controller = "Product", action = "List", page = 1 }
            );

Here is the controller method :

 public ViewResult List(string category, int page = 1)
{
}

However , if I use :

http://localhost:44123/chess?page=2

Then I see that page=1 ( not 2 , as I expected):

enter image description here

BTw - if I change the route to :

 routes.MapRoute("Default23",
                "{category}",
                new { controller = "Product", action = "List"  }
            );

Then I do see the right value:

enter image description here

Why is it happening ? all I wanted is to set a default value if I don't set a value . Why does setting a default value , prevents reading the query string value ?


Solution

  • To explain the behavior, the 3rd argument of MapRoute is (my emphasis)

    An object that contains default route values.

    By specifying new { controller = "Product", action = "List", page = 1 } you are defining a route value for page (even though its not a segment in your url definition) and giving it a default value of 1.

    Now when you navigate to ../chess?page=2 it matches your Default23 route, and the value of 'chess' is assigned to the {category} segment, but nothing is assigned to page because there is no segment for {page} (its a query string value).

    When your List(string category, int page = 1) method is executed, the DefaultModelBinder evaluates values for binding in the following order

    1. Previously bound action parameters, when the action is a child action
    2. Form values
    3. JSON Request body (ajax calls)
    4. Route data
    5. Query string parameters
    6. Posted files

    For a GET, 1, 2, 3 and 6 are not applicable, so the DefaultModelBinder first evaluates the Route data (RouteData.Values) and finds a value of "chess" for category (from the url). It also finds a value of "1" for page (because you defined a default value for it in the route definition).

    At this point you have category="chess", page=1.

    The DefaultModelBinder then evaluates the Query string parameters (Request.QueryString) and finds a value of "2" for page, but because page already has been set, its ignored. By default, the DefaultModelBinder binds the first match it finds and ignores all subsequent matches (unless binding to an IEnumerable property).

    So at this point (the end of the binding process) you still have category="chess", page=1.