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
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.
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.