ASP.NET MVC Routing – Url.Action sending query string not route values

I have been searching for a solution to this problem for a couple of days but it has me stumped.

I’ve got an ASP.NET MVC app where I have implemented paging, sorting and filtering. It was working fine in the first view, then when I went to implement it in the next view it stopped working. I was following this guide.

Prior to the changes if I used the filter it would post back and hit the correct action and filter the results. Clicking on one of the page numbers at the bottom would take me to the page in the filtered result set.

After the change I got an error as there was a clash with the route matching more than one. As the route values will be the same for both of these controllers I then thought I’d need to add the controller name as the first part of the route.

Now when I apply a filter it still works, but then on clicking another page number removes the filter but goes the the correct page number (on an unfiltered result set). Debugging show me it is going to the default Index action rather than the defined route that will apply the filtering and sorting.

The difference is that prior to the change it was sending the below url:

https://localhost:44382/Users/Index/UserName/ascending/none/all/2/an

now it is sending:
https://localhost:44382/Users/Index?sortKey=UserName&sortDirection=ascending&previousSortKey=none&selectedFbSupplied=all&page=2&selectedNameFilter=an

If I manually change it to the route values rather that the query string it works as expected.

Relevant parts of the code below.

From the UsersController:

    [HttpGet]
    [Route("Users/Index")]
    public ActionResult Index(int? page)
    {
        ViewBag.SortKey = "UserName";
        ViewBag.SortDirection = "ascending";
        ViewBag.SelectedFbSupplied = string.IsNullOrEmpty(ViewBag.SelectedFbSupplied) ? "all" : ViewBag.SelectedFbSupplied;
        ViewBag.SelectedNameFilter = string.IsNullOrEmpty(ViewBag.SelectedNameFilter) ? "" : ViewBag.SelectedNameFilter;

        ViewBag.FbSupplied = new List<SelectListItem>{
                                                       new SelectListItem { Value="all", Text="All"},
                                                       new SelectListItem { Value="yes", Text="Yes"},
                                                       new SelectListItem { Value="no", Text="No"}
                                                    };

        var users = SortedUserList(FilteredUsers());
        int pageSize = 50;
        int pageNumber = (page ?? 1);
        return View(users.ToPagedList(pageNumber, pageSize));
    }

    [HttpGet]
    [Route("Users/Index/{sortKey}/{sortDirection}/{previousSortKey}/{selectedFbSupplied}/{page:int}/{selectedNameFilter?}")]
    public ActionResult Index(string sortKey, string sortDirection, string previousSortKey, string selectedFbSupplied, int? page, string selectedNameFilter="")
    {
                    
        if (sortKey == previousSortKey)
        {
            //Key is the same, flip the direction
            sortDirection = sortDirection == "ascending" ? "descending" : "ascending";
        }
        ViewBag.SortKey = String.IsNullOrEmpty(sortKey) ? "UserName" : sortKey;
        ViewBag.SortDirection = String.IsNullOrEmpty(sortDirection) ? "ascending" : sortDirection;


        ViewBag.FbSupplied = new List<SelectListItem>{
                                                       new SelectListItem { Value="all", Text="All"},
                                                       new SelectListItem { Value="yes", Text="Yes"},
                                                       new SelectListItem { Value="no", Text="No"}
                                                    };

        var nameFilter = string.IsNullOrEmpty(selectedNameFilter) ? "" : selectedNameFilter;
        ViewBag.SelectedFbSupplied = string.IsNullOrEmpty(selectedFbSupplied) ? "all" : selectedFbSupplied;
        ViewBag.SelectedNameFilter = nameFilter;


        var users = SortedUserList(FilteredUsers(nameFilter, selectedFbSupplied), sortKey, sortDirection);
        int pageSize = 50;
        int pageNumber = (page ?? 1);
        return View(users.ToPagedList(pageNumber, pageSize));
    }

And the page links in the view:
@Html.PagedListPager(Model, page => Url.Action("Index",  "Users" , new { sortKey = ViewBag.SortKey, sortDirection = ViewBag.SortDirection, previousSortKey = "none", selectedFbSupplied = ViewBag.SelectedFbSupplied, page = page , selectedNameFilter = ViewBag.SelectedNameFilter}))

Route config (unchanged):
public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        routes.MapMvcAttributeRoutes();

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }

}

Any ideas why it is sending a query string and not route values?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

In short: If you specify route names (not just route templates) then you can refer to them through their name via the RouteUrl helper.

Route templates

  • It is a pattern, which defines how a given action can be reached and how should route parameters be matched.
  • They are registered into a RouteTable, which is basically a mapping between a given url pattern and the associated controller’s action.
  • The ordering of the routes matters because the first matching wins.

Route names

  • They have nothing to do with Url pattern matching.
  • They are only used during Url generation. (RouteUrl, CreatedAtRoute (1), etc.)
  • They must be globally unique (so ordering does not matter).

Possible errors

  • If you register two routes with the same Template then the application will throw an AmbiguousMatchException at runtime when you make a call against one of the route.
    • So, other routes will work perfectly fine.
  • If you register two routes with the same Name then the application will throw an InvalidOperationException at runtime when you make any call.
    • So, other routes will not work.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x