views:

127

answers:

3

Hi,

I have a controller named Movie, with an action named ByYear, which takes the year as a parameter :

    public ActionResult ByYear(int year)
    {
        ViewData["Title"] = string.Format("Movies released in {0}", year);
        var repository = MvcApplication.GetRepository();
        var movies = repository.Medias
                                .OfType<Movie>()
                                .Where(m => m.Year == year);
        return View("Index", movies);
    }

I'd like to access this action with the following URL : /Movie/ByYear/{year}, but the only valid route for this action is this : /Movie/ByYear?year={year}.

I tried to add new routes in my application's RegisterRoutes method, but I can't find a way to get the desired result...

Could anyone tell me how to achieve that ?

Note: this is actually very similar to this question, but no answer was accepted, and the highest voted answer makes no sense to me as I'm completely new to MVC...

+6  A: 

Change the name of your parameter year to id and this will match the default route that MVC adds to your project.

So for further clarification, let's take a look at the default route added by ASP.NET MVC:

routes.MapRoute(
    "default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = "" }
);

In this route you can see three tokens that are named specifically for controller, action, and the third token which is passed to the action is id. When a request comes into your application, ASP.NET MVC will analyze the routes that are currently mapped and try to find a method signature that matches them by using reflection against your controllers.

When it looks at your Movie controller, it sees an action called ByYear, however that method takes an integer called year, not id. This is why you end up with something like /Movie/ByYear?year={year} when you create an ActionLink for that particular Action. So to fix this, you have two options:

The first and most simple method to fix this is to just change the method signature for your Action to accept a parameter named id which is what I recommended above. This will work fine, but I can see where it might cause a little bit of confusion when you go back to that source later and wonder why you called that parameter id.

The second method is to add another route that matches that method signature. To do this, you should open your Global.asax and just add the following (untested, but should work):

routes.MapRoute(
    "MoviesByYear",
    "Movies/ByYear/{year}",
    new { controller = "Movie", action = "ByYear" }
);

This route is hard-coded, yes, but it won't break the other routes in your system, and it will allow you to call the method parameter year.

EDIT 2: Another thing to note is that the routing engine will stop on the first route it finds that matches your request, so any custom routes like this should be added before the default route so you are sure they will be found.

Scott Anderson
But I don't *want* to call it id... it's not an id, it's a year. I don't want to ruin the clarity of my code just to make the MVC routing system happy ;). Anyway, I found the answer myself minutes after posting my question...
Thomas Levesque
@Thomas. The action is called ByYear. I don't think the clarity will be ruined by changing the variable parameter. On the other hand, moving away from the default routes doesn't help maintainability.
Dan Atkinson
Yes, the solution with the new route is what I eventually came up with... But does that mean I will have to create new routes every time I create an action with new parameter names ?! Thanks for the explanation anyway
Thomas Levesque
Yes, which is why most folks decide to go with `id` :) -- Adding more routes isn't always the best solution, but it's not necessarily *evil*. Just be careful and be sure to test them thoroughly.
Scott Anderson
Also, if you think about it, `id` stands for identifier, right? What are you using to identify Movies in the ByYear action?
Scott Anderson
OK, you have a point... I will probably consider this solution ;)
Thomas Levesque
Also, nothing's stopping you from adding a comment on top of the method signature :P `// used "id" here to match the default route`
Scott Anderson
By the way, how bad is the performance impact of creating many routes ?
Thomas Levesque
This article has some nice insight into ASP.NET MVC performance and specifically routing: http://codeclimber.net.nz/archive/2009/04/17/how-to-improve-the-performances-of-asp.net-mvc-web-applications.aspx
Scott Anderson
+4  A: 

OK, I just found out how to do it. I just had to create the new route before the default route... I didn't think the order had any significance

        routes.MapRoute(
            "MovieByYear",                                          // Route name
            "Movie/ByYear/{year}",                                  // URL with parameters
            new { controller = "Movie", action = "ByYear" }         // Parameter defaults
        );

EDIT: Isn't there a simpler way ? (not involving renaming the parameters). I'd like to be able to do something like that :

[Route("/Movie/ByYear/{year}")]
public ActionResult ByYear(int year)
{
    ...
Thomas Levesque
Although this is correct, I would be inclined to go with Scott's answer in order to maintain a minimal number of routes as possible. And the change required to do that would be pretty simple.
Dan Atkinson
Yes, I see your point... but naming a parameter "id" when it's actually a year can be really confusing, and I definitely don't want that.
Thomas Levesque
There isn't a simpler way as it needs to know the controller and unfortunately doesn't know how to get that from the URL. I've had this problem also and didn't like calling all my controller method parameters "id" just to match the routing table, so I did what you did
Chris S
routing via method attributes is definitely my preferred way...http://stackoverflow.com/questions/894779/asp-net-mvc-routing-via-method-attributes
dotjoe
+1  A: 

Design considerations aside, if you did not want to rename the parameter, you could add something like the route below, which enforces having the year parameter

routes.MapRoute(
     "MovieByYear",                        // Route name
     "Movie/ByYear/{year}",       // URL with parameters
     new { controller = "Movie", action = "ByYear" },
     new { year = @"\d+" }                 // Parameter defaults
);
Bob Palmer
This route will match much more than the Movie controller and ByYear action. I find that routes should be as specific as possible when you add new ones, simply because there are a lot of side effects.
Scott Anderson
+1 for the regex constraint... I really have a lot to learn about MVC ;)
Thomas Levesque