6

This is the controller class. I am showing only method signatures.

[Authorize]
[RoutePrefix("specification")]
[Route("{action=index}")]
public class SpecificationController : BaseController
{
    [HttpGet]
    [Route("~/specifications/{subcategoryID?}")]
    public ActionResult Index(int? subcategoryID);

    [HttpPost]
    [Route("get/{subcategoryID?}")]
    public JsonResult Get(int? subcategoryID);

    [HttpGet]
    [Route("~/specifications/reorder/{subcategoryID}")]
    public ActionResult Reorder(int subcategoryID);

    [HttpGet]
    [Route("new/{id?}")]
    public ActionResult New(int? id);

    [HttpGet]
    [Route("edit/{id?}")]
    public ActionResult Edit(int id);

    [HttpPost]
    [ValidateAntiForgeryToken]
    [Route("edit")]
    public JsonResult Edit(SpecificationJson specification);

    [HttpPost]
    [Route("moveup")]
    public JsonResult MoveUp(int specificationID);

    [HttpPost]
    [Route("movedown")]
    public JsonResult MoveDown(int specificationID);

    [HttpDelete]
    [Route]
    public ActionResult Delete(int id);
}

The problem is that calling

@Url.Action("index", "specifications", new RouteValueDictionary() { { "subcategoryID", @subcategory.SubcategoryID } })

returns

/specifications?subcategoryID=15

instead of

/specifications/15

Why is this happening? I do not have any similar methods on that route expect this one!

Robert
  • 2,407
  • 1
  • 24
  • 35

2 Answers2

3

Your call to generate the URL is incorrect. To match the controller name, it should be "specification" not "specifications".

@Url.Action("index", "specification", new { subcategoryID=subcategory.SubcategoryID })

Keep in mind, the URL specified in the [Route] attribute is only cosmetic. Your route values must match the controller name and action method name for it to utilize that route to generate the URL.

To make this more clear for those maintaining the code (and slightly faster), it might be better to make the parameter values Pascal case just like the controller and action names.

@Url.Action("Index", "Specification", new { subcategoryID=subcategory.SubcategoryID })

Why is this happening?

-------------------------------------------------------------
| Route Key          | Route Value   | Your Action Request  |
|--------------------|---------------|----------------------|
| Controller         | Specification | Specifications       | No Match
| Action             | Index         | Index                | Match
| subcategoryID      | ?             | XXX                  | Match (Always)
-------------------------------------------------------------

To get a route match, all parameters of @Url.Action must match the route value dictionary. The problem is that Controller=Specifications is not defined in the route value dictionary because your actual controller's name is SpecificationController. Therefore, the route value name is Specification regardless of what you put in the [Route] attribute. The URL ~/specifications/{subcategoryID?} has nothing at all to do with an outgoing (URL generation) match - it only matches incoming URLs and determines what the URL will look like when it is generated.

If you want to use Specifications instead of Specification for the route value, you need to move the action method to a new controller named SpecificationsController. That said, I don't see what difference it makes, since the end user won't see the route value name anyway.

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • I know that if I do it as you said it will work. It was like that previously. For more consistency I decided that when I am returning view with all the results it should say /specifications rather than /specification since the later one implies that it is only one specification we are talking about. Further more, I think by using ~/specifications in the routing attribute I made it clear that I am restarting the route and not using the route prefix specified in the controller. – Robert Jan 23 '17 at 22:12
  • 1
    Yes, but in order for that to work, you have to move the action method to a controller named `SpecificationsController`. By not specifying it correctly, you are getting a route miss and it is matching another route, which is why you are getting a query string. – NightOwl888 Jan 23 '17 at 22:15
  • This worked like a charm. I thought rewriting the route would completely reqrite it. It looks like you are right! – Robert Jan 24 '17 at 17:29
0

You have to use this in order to generate follow url: /specifications/15

@Url.Action("index", "specifications", new { subcategoryID=subcategory.SubcategoryID })

[Route("~/specifications/{subcategoryID?}")]
public ActionResult Index(int? subcategoryID);

What did I do wrong and how can I revert to using subcategoryID as parameter name ?

You have to add another route (before DEFAULT ROUTE) in order to have another optional parameter:

Something like this:

 routes.MapRoute(
         "SecondRoute",
         "{controller}/{action}/{subcategoryID}",
          defaults: new { controller = "Home", action = "Index", subcategoryID = UrlParameter.Optional }
 );
Mihai Alexandru-Ionut
  • 47,092
  • 13
  • 101
  • 128