0

I have creating URL using RouteLink.

@Html.RouteLink("Edit", "PageWithId",
new
{
    controller = "Customers",
    action = "Edit",
    id = item.CustomerID,
    page = ViewBag.CurrentPage
}) 

I am using this routing PageWithId with route link

routes.MapRoute(
    name: "PageWithId",
    url: "{controller}/{action}/{page}/{id}",
    defaults: new { controller = "Customers", action = "Edit", page = UrlParameter.Optional, id = UrlParameter.Optional }
);

I have 3 routing code. Here is all

routes.MapRoute(
    name: "PageWithSort",
    url: "{controller}/{action}/{page}/{SortColumn}/{CurrentSort}",
    defaults: new { action = "Index", page = UrlParameter.Optional, SortColumn = UrlParameter.Optional, CurrentSort = UrlParameter.Optional }
);

routes.MapRoute(
    name: "PageWithId",
    url: "{controller}/{action}/{page}/{id}",
    defaults: new { controller = "Customers", action = "Edit", page = UrlParameter.Optional, id = UrlParameter.Optional }
);

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

When I run my program the route link generate URL like http://localhost:55831/Customers/Edit/1/ALFKI

When I click on the link the Edit action is getting called but customer id is getting null where as ALFKI is there in URL as customer id.

Here is my edit action details

public ActionResult Edit(string id, int page)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Customer customer = db.Customers.Find(id);
    if (customer == null)
    {
        return HttpNotFound();
    }
    ViewBag.CurrentPage = page;
    return View(customer);
}

Please tell me why id is getting null when ALFKI as passing as customer id?

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
Monojit Sarkar
  • 2,353
  • 8
  • 43
  • 94

2 Answers2

1

A placeholder (such as {page} or {controller}) acts like a variable. It can match anything and store it in a route value with that same name.

So, what you are actually saying with your configuration is:

Try

/<anything (optional)>/<anything (optional)>/<anything (optional)>/<anything (optional)>/<anything (optional)>

If that doesn't work, try

/<anything (optional)>/<anything (optional)>/<anything (optional)>/<anything (optional)>

If that doesn't work, try

/<anything (optional)>/<anything (optional)>/<anything (optional)>

If that doesn't work, since {id} is optional try

/<anything (optional)>/<anything (optional)>

If that doesn't work, since {action} is optional try

/<anything (optional)> (if it matches, action will be "Index")

If that doesn't work, since {controller} is optional try

/ (if it matches, controller will be "Home" and action will be "Index")

See the problem? The first route can (and will) match a URL of any value and any length from 0 to 5 segments. Once it matches, there is no way to try another route that follows it.

You need to constrain the routes in some way to ensure some URLs can pass through to the next route to try, namely in the cases where you don't want to match that URL.

Also, segments can be made optional, but they cannot change position. If you pass a value that means CurrentSort, that automatically means that all of the parameters to the left of it are required.

To me it seems illogical to make {page}, {SortColumn} and {CurrentSort} optional. If you don't need them then either use a different route or take them out of the path altogether and use a query string instead (routes are unaffected by query string values, they only match on the path).

To make the values required, remove the value = UrlParameter.Optional from the defaults. In fact, you can just remove all of the defaults because controller and action are already passed in the URL.

routes.MapRoute(
    name: "PageWithSort",
    url: "{controller}/{action}/{page}/{SortColumn}/{CurrentSort}"
);

routes.MapRoute(
    name: "PageWithId",
    url: "{controller}/{action}/{page}/{id}"
);

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

Although these routes can still match anything, this will constrain them to a specific length and since all 3 of them have a different number of segments they won't conflict. But since they are still so broad, you should think about using another technique to constrain them such as literal segments or route constraints. For example:

routes.MapRoute(
    name: "CustomerEditWithId",
    url: "Customers/Edit/{page}/{id}",
    defaults: new { controller = "Customers", action = "Edit" }
);
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
0

The first route PageWithSort is applied instead of the second one.

Note that the routing framework does not know which parameter name you used to generate the URL. It just looks at the received string Customers/Edit/1/ALFKI.

For this string, the first route "PageWithSort" matches with

  • controller = Customers
  • action = Edit
  • page = 1
  • SortColumn = ALFKI
  • CurrentSort = null (this is OK because it is optional)

Because the first matching route will be used, this URL will never hit the "PageWithId" route.

Maybe you can make the SortColumn and CurrentSort parameters of "PageWithSort" required?

Or change the order in which the routes are defined.

Or add something unique to the "PageWithSort" route, e.g.

routes.MapRoute(
        name: "PageWithId",
        url: "{controller}/{action}/{page}/ID/{id}",
        //                                 ^^
        defaults: new { controller = "Customers", action = "Edit", page = UrlParameter.Optional, id = UrlParameter.Optional }
    );

Adding /ID as hard coded part of the URL will make it distinguishable from the other routes (assuming there is not SortColumn calling "ID").

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
Georg Patscheider
  • 9,357
  • 1
  • 26
  • 36