views:

378

answers:

3

[NOTE: I'm using ASP.NET MVC2 RC2.]

I have URLs like this:

/customers/123/orders/456/items/index

/customers/123/orders/456/items/789/edit

My routing table lists the most-specific routes first, so I've got:

// customers/123/orders/456/items/789/edit
routes.MapRoute(
    "item", // Route name
    "customers/{customerId}/orders/{orderId}/items/{itemId}/{action}", // URL with parameters
    new { controller = "Items", action = "Details" }, // Parameter defaults
    new { customerId = @"\d+", orderId = @"\d+", itemId = @"\d+" } // Constraints
);

// customers/123/orders/456/items/index
routes.MapRoute(
    "items", // Route name
    "customers/{customerId}/orders/{orderId}/items/{action}", // URL with parameters
    new { controller = "Items", action = "Index" }, // Parameter defaults
    new { customerId = @"\d+", orderId = @"\d+" } // Constraints
);

When I'm in the item "Edit" page, I want a link back up to the "Index" page. So, I use:

ActionLink("Back to Index", "index")

However, because there's an ambient item ID, this results in the URL:

/customers/123/orders/456/items/789/index

...whereas I want it to "forget" the item ID and just use:

/customers/123/orders/456/items/index

I've tried overriding the item ID like so:

ActionLink("Back to Index", "index", new { itemId=string.empty })

...but that doesn't quite work. What that gives me is:

/customers/123/orders/456/items?itemId=789

How can I persuade ActionLink to "forget" the item ID?


EDIT: fixed question - I was referring to "order" where I meant "item".

EDIT: added detail of why itemId=string.empty doesn't work.

+1  A: 

I deleted my previous answer, didn't notice they were the same controller. Try setting itemId = string.empty. This will make the first route fail, as it requires a digit in that position.

ActionLink("Back to Index", "index", new { itemId=string.empty })

Optionally you can create a link with the name of the route "item" manually

<%= Url.RouteUrl("item", new { controller="Items", action="Index", orderId=3 ... }) %>
Jab
@Jab: please see my edited answer as to why itemId=string.empty doesn't quite do the trick.
Gary McGill
@Jab: RouteUrl does work, but it's a total pain to have to explicitly specify all the route values. (ActionLink has made me lazy). However, I did have success with RouteLink - see my answer.
Gary McGill
A: 

OK, I think I've got this to do what I want, by using RouteLink instead of ActionLink:

RouteLink("Back to Index", "items", new { action="index" })

...where the 2nd parameter is the name of the route I want to explicitly invoke.


EDIT: I didn't really want to go through all my dozens of ActionLinks and put them in this form, so instead I've modified the "item" rule to include a constraint on the {action} parameter, so that "index" does not match this route. Thus, it matches the "items" route instead - which has no itemId parameter. That seems to be a simpler way to do this, since the bulk of my code is unchanged.

Gary McGill
+1  A: 

In your constraint you can delete any routeValues you do not need.

values.Remove("itemId");

Then create a class that inherits from IRouteConstraint.

Then in the Match method clear your route value and return true (indicates the route matches).

public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{

    values.Remove("itemId");    
    return true;
}

then in Global.asax.cs change

new { customerId = @"\d+", orderId = @"\d+" }

to

new { customerId = @"\d+", orderId = @"\d+", custom=new MyNewConstraint() }
Cheddar
Sounds good, but what do you mean "in your constraint"? Where does this bit of code go?
Gary McGill
Wow. I salute your knowledge of the routing infrastructure. I guess I could create a constraint class that accepts a list of (names of) route values to ignore in the constructor. But, it still seems like too much of an intervention to me - both in terms of the amount of code written and the extent to which you have to go against the grain. Personally, I think this should work out of the box - it seems like an oversight. I'll accept your answer, but actually I've "solved" the problem by putting a constraint on the action parameter as per my own answer. A zero-code solution is easiest for me.
Gary McGill
I'll agree with that. How much of our jobs is making it work like it should though?
Cheddar