0

In my layout page, the links to the main sections that make up my site are rendered with a call like this:

@SiteSectionLink("index", "blog", "blog")

Where SiteSectionLink is a helper that looks like this:

@helper SiteSectionLink(string action, string controller, string display)
  {
  <li>
    <h1>
      <a class="site-section" href="@Url.Action(action, controller)">@display</a></h1>
  </li>
}

On the actual blog page, all links also refer to the "Index" action but also specify either a date parameter (such as "blog/4-2011" or "blog/2010") that is used to filter the posts by a date period. In addition to that, there's also an optional postID parameter that is used to refer to a specific post.

To accomplish that, I have the following routes:

routes.MapRoute(
 "Blog", 
 "blog/{date}/{postID}", 
  new 
  { 
    controller = "blog", 
    action = "index", 
    date = UrlParameter.Optional, 
    postID = UrlParameter.Optional 
  } 
);

routes.MapRoute(
  "Default", // Route name
   "{controller}/{action}/{id}", // URL with parameters
    new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

Now, the problem is that when I have clicked a link that is something like "blog/11-2010" or "blog/11-2010/253" then the link in my layout page that refers to my blog in general now also refers to that same URL when I want it to just link to "blog/", not "blog/11-2010".

If I change the SiteSectionLink helper to explicitly pass in null for date and postID like this:

<a class="site-section" href="@Url.Action(action, controller, 
  new { date = (string)null, postID = (int?)null})">@display</a></h1>

The current route values are still used but now it looks like "blog?date=11-2010".

I saw this similar question but the accepted answer doesn't work for me, and I don't use ActionLink in the first place and I suspect that ActionLink would use Url.Action under the hood.

Community
  • 1
  • 1
JulianR
  • 16,213
  • 5
  • 55
  • 85

2 Answers2

3

While the issue you are experiencing is not quite the behavior detailed by Phil Haack in this blog post regarding a bug with MVC3 routing and a route with two optional parameters, I would suggest applying the fix described in Phil's post.

I also would suggest never creating a route with two optional parameters, and instead follow the pattern of breaking the desired routing into two separate routes.

counsellorben
  • 10,924
  • 3
  • 40
  • 38
  • Thank you for the improvement in splitting up the routes, you're right that having two optionals can only cause headaches. – JulianR Sep 19 '11 at 18:15
  • On second thought, I'm marking this as the answer because it's the splitting up that solved it, in combination with adding an overload to `SiteSectionLink`. – JulianR Sep 19 '11 at 18:17
1

Yes Url.Action method puts the parameters in the querystring. You can change your helper like this:

@helper SiteSectionLink(string action, string controller, string display, string date = null, string id=null)
{ 
  <li> 
    @if (date == null)
    {
        <h1><a class="site-section" href="~/blog/@controller/@action">@display</a></h1> // simple workaround or better use P. Haack workaround
    }
    else 
    {
        <h1><a class="site-section" href="@Url.RouteUrl("blog", new { action = @action, controller = @controller, date = @date, id = @id })">@display</a></h1> 
    }
  </li> 
} 

So you can use SiteSelectionLink like these:

@SiteSectionLink("Index", "Blog", "test", "2011", "4")
@SiteSectionLink("Index", "Blog", "test2", "2011")
@SiteSectionLink("Index", "Blog", "test3")
Massimo Zerbini
  • 3,125
  • 22
  • 22
  • Thanks, I went for adding an optional `routeValues` parameter to `SiteSectionLink` that sets date and postID to null. I'm still curious why it behaves this way though, when is this behaviour ever wanted? – JulianR Sep 19 '11 at 18:14
  • `Url.Action` method uses querystring parameters when we supply values for properties that do not correspond with segment variables. In your case, I think it's due to the way that mvc engine decode the url to the route "blog", matching parameters that are not passed in the anonymous type. – Massimo Zerbini Sep 20 '11 at 13:11