views:

449

answers:

4

Why does the following Html.ActionLink call:

Html.ActionLink("Approve", "Advance", new { id = Model.ID, step = StepType.Approve })

generate a URL with query parameters rather than a "restful" URL, i.e.:

http://localhost/Website/Case/Advance/1?step=Refer

I only have the default route registered, do I need additional routes that can understand what the "StepType" parameter is?

I've tried adding this route in after the default route:

routes.MapRoute(
    "CaseAdvance",
    "{controller}/{action}/{id}/{step}",
    new {controller = "Case", action = "Advance", id = "", step = StepType.Refer});

but it had no effect. Adding the new route registration before the default gave me an error:

The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int64' for method 'System.Web.Mvc.ActionResult Advance(Int64, Website.Core.StepType)' in 'Website.Controllers.CaseController'. To make a parameter optional its type should be either a reference type or a Nullable type.

+2  A: 

Yes, if there is no route such as "{controller}/{action}/{id}/{step}" then the ActionLink method will simply pass "step" as a querystring parameter.

dhulk
Hmm, I've tried adding a new route (before and after the default one) and either I get an exception or it makes no difference. Where can I find out what the route definition should look like?
Neil Barnwell
+1  A: 

In your route you have specified a parameter of stepType but your passing a parameter called step.

Your actionlink parameters names must match the route parameter names or you will get exactly what your seeing.

EDIT: Ok, you changed your code while I was typing this answer!!

Simon
Thanks - I've fixed that (I'm an idiot) but unfortunately still getting an exception if the new (more specific) route is registered first.
Neil Barnwell
+1  A: 

Try using RouteLink and see if that works for you.

Html.RouteLink("Approve", "CaseAdvance", new { controller = "Case", action = "Advance", id = Model.ID, step = StepType.Approve })

If calling RouteLink produces a valid link it will at least mean your route is setup correctly.

Mark
+1  A: 

Custom route catching too much at the moment

Your exception only tells you that your custom route is catching something that you didn't intend it to catch. So if there would be a request to your application root URL:

http://localhost/Website

your custom route would catch it. And set it's defaults. And call your CaseController.Advance() action. And of course throw an exception, because id is not defined.

Keep your custom route in front of default one

But you will have to change your custom route or add route constraints to it so it will actually catch only those requests that it's meant to catch.

But which change should you do? If there's only going to be a single controller that needs it than change it to:

routes.MapRoute(
    "CaseAdvance",
    "Case/{action}/{id}/{step}",
    new { controller = "Case", action = "Advance", id = "", step = StepType.Refer});

If there are other controllers a well, you can keep it as it was, just add constraints:

routes.MapRoute(
    "CaseAdvance",
    "{controller}/{action}/{id}/{step}",
    new { controller = "Case", action = "Advance", id = "", step = StepType.Refer},
    new { controller = "Case|Other" });

If there can be any controller, you can make a requirement for your id to be numeric:

routes.MapRoute(
    "CaseAdvance",
    "{controller}/{action}/{id}/{step}",
    new { controller = "Case", action = "Advance", id = "", step = StepType.Refer},
    new { id = @"\d+" })

In this case this route will only catch those requests that actually have an id defined. As a number of course.

You will know which one suits you best.

Robert Koritnik