Why is this route parameter tacked onto the querystring?
Asked Answered
P

2

6

I have an ASP.NET MVC 3 application that records a user's pedometer entries. A user can view all most recent pedometer entries by visiting /Pedometer and can filter by year, year/month, or year/month/date by visiting URLs like /Pedometer/2011, /Pedometer/2011/08 and /Pedometer/2011/08/15, respectively.

I've created two mapped routes in Global.asax. The first route, shown below, is what allows the various URL patterns for filtering by date. The second route (not shown) is the default ASP.NET MVC route.

routes.MapRoute(
    "PedometerEntries", // Route name
    "Pedometer/{year}/{month}/{day}", // URL with parameters
    new
    {
        controller = "Pedometer",
        action = "Index",
        year = UrlParameter.Optional,
        month = UrlParameter.Optional,
        day = UrlParameter.Optional
    }, // Parameter defaults
    new
    {
        year = @"\d{4}",
        month = @"([012]?\d{1})?",
        day = @"(([1-9])|([12][0-9])|(3[0-1]))?"
    } // Parameter constraints
);

Here's my question. I have a view where I want to create a link of the form: currentUrl?format=csv, which will let the user download the pedometer entries for the requested URL in a CSV format. So if a user is visiting /Pedometer, the download link would be to /Pedometer?format=csv. If the user is visiting /Pedometer/2011/08 the download link would be to /Pedometer/2011/08?format=csv.

To create such a link I added a custom Html Helper named DownloadToExcel with the following code:

public static MvcHtmlString DownloadToExcel(this HtmlHelper helper, string linkText)
{
    RouteValueDictionary routeValues = helper.ViewContext.RouteData.Values;

    // Add the format parameter to the route data collection, if needed
    if (!routeValues.ContainsKey("format"))
        routeValues.Add("format", "csv");

    return helper.ActionLink(linkText,                          // Link text
                                routeValues["action"].ToString(),  // Action
                                routeValues);                      // Route values
}

When I add the @Html.DownloadToExcel() markup in my view, it generates a link, but here's the rub - when the user visits the recent entries or the entries filtered by year/month or year/month/date, it works as expected, but not when the user visits the year filter URL.

The following list shows the URL the user visits and the corresponding URL generated by the custom Html Helper:

  • Visiting: /Pedometer - Download link: /Pedometer?format=csv
  • Visiting: /Pedometer/2011 - Download link: /Pedometer?year=2011&format=csv
  • Visiting: /Pedometer/2011/08 - Download link: /Pedometer/2011/08?format=csv
  • Visiting: /Pedometer/2011/08/15 - Download link: /Pedometer/2011/08/15?format=csv

Why is it when visiting /Pedometer/2011 the download link is /Pedometer?year=2011&format=csv and not /Pedometer/2011?format=csv? And why does it not work for that one case but works as expected for the year/month and year/month/date cases?

Thanks

Panelist answered 16/8, 2011 at 22:19 Comment(0)
G
6

This problem is most likely caused by this bug described by Phil Haack on his blog. There's a regression bug introduced in ASP.NET MVC 3 when you have two consecutive optional URL parameters.

Gummous answered 16/8, 2011 at 23:46 Comment(0)
Z
4

I created a small MVC 3 application with the code you provided and got exactly the same behavior as described.

If I go to http://localhost:51181/pedometer/2011 the generated link would be http://localhost:51181/Pedometer?year=2011&format=csv.

But if I explicitly entered the name of the action (Index) it would render correctly.

Visiting http://localhost:51181/pedometer/index/2011 will generate the following link:

http://localhost:51181/pedometer/index/2011?format=csv

It seems the correct route is not always used by the HtmlHelper extension method.

If I add the following route just below your custom route, but before the default MVC route it works OK.

routes.MapRoute(
    "PedometerDefault",
    "Pedometer/{year}",
    new { controller = "Pedometer", action = "Index", 
          year = UrlParameter.Optional }
);
Zygapophysis answered 16/8, 2011 at 23:32 Comment(2)
This looks like the same workaround recommended by Phil Haack in the blog post in @George Stocker's answer.Routine
Yes, seems so. Did not know of the bug before that.Zygapophysis

© 2022 - 2024 — McMap. All rights reserved.