views:

65

answers:

3

I've defined the following route for a simple blog.

routes.MapRoute(
  "Blog",
  "blog/{year}/{month}/{day}",
  new { controller = "Blog", action = "Index" },
  new { year = @"\d{4}", month = @"\d{2}", day = @"\d{2}" }
);

The url should be able "hackable" to accomplish the following:

I have created a controller which handles this action quite nicely. However I am having trouble creating links in my views using Url.Action().

For example this...

var d = new DateTime(2010, 1, 25);
Url.Action("Index", "Blog", new { year=d.Year, month=d.Month, day=d.Day} );

...generates a url like that looks like this:

http://abc.com/blog?year=2010&month=2&day=21

I would rather like it to generate a url that looks like the urls in the list above.

http://abc.com/blog/2010/02/21

Is there any way I can use Url.Action() or Html.ActionLink() to generate urls in the format I desire?

+3  A: 

Hi,

The issue there is that the month you're passing in to Url.Action is a single-digit month, and thus doesn't match the month constraint on the route definition. Constraints are typically run not only on incoming URLs but also when generating URLs.

The fix would be to manually call .ToString() on the month and format it as a two-digit number. You'll need to do the same for the day as well. For the year it's not an issue since all the years in our lifetimes will be four-digit numbers.

Here's sample code to format numbers:

int month = 2;
string formattedMonth = month.ToString("00", CultureInfo.InvariantCulture);
// formattedMonth == "02"

Please note that when formatting the number manually that you must use the Invariant Culture so that different cultures and languages won't affect how it gets formatted.

You'll also need to set default values for month and day so that they are not required in the URL:

routes.MapRoute( 
  "Blog", 
  "blog/{year}/{month}/{day}", 
  new { controller = "Blog", action = "Index", month = "00", day = "00" }, 
  new { year = @"\d{4}", month = @"\d{2}", day = @"\d{2}" } 
);

And in your controller action check if the month or day are 0, in which case you should show an entire month or entire year.

Eilon
You nailed it. Thanks for your answer. It works like a charm.
jessegavin
A: 

The other issue I see you running into is that you'll need several other route entries with appropriate defaults to handle those other scenarios.

"http://abc.com/2010" will not match "blog/{year}/{month}/{day}". That's a very specific route that requires three parameters (with constraints) to match. In order to accept those other routes you'll need to create other route entries along the lines of:

routes.MapRoute(
  null,
  "blog",
  new { controller = "Blog", action = "Index", year = 0000, month = 00, day = 00 },
  new { year = @"\d{4}", month = @"\d{2}", day = @"\d{2}" }
);

routes.MapRoute(
  null,
  "blog/{year}",
  new { controller = "Blog", action = "Index", month = 00, day = 00 },
  new { year = @"\d{4}" }
);

routes.MapRoute(
  null,
  "blog/{year}/{month}",
  new { controller = "Blog", action = "Index", day = 00 },
  new { year = @"\d{4}", month = @"\d{2}"}
);

There are a few ways to handle this scenario, but now in your Blog controller and your Index action you can filter the posts results by year, month, day.

Quick example:

if(year == 0000 && month == 00 && day == 00)
    var posts = postsRepository.GetAllPost();
else if(year != 0000 && month == 00 && day == 00)
    var posts = postsRepository.GetPostsByYear(year);

...

DM
I would suggest that instead of having three separate routes to only use default values for month and day. I have updated my answer to include this.
Eilon
+1 for brevity and overall awesomeness. If I'm being honest after some pretty heavy MVC usage I didn't know that was possible. I thought if the route was accepted it sent the default values along no matter what, but after a quick test your shorthand solution works great.
DM
You don't need multiple routes to accomplish this. The route I show in my question description works wonderfully. (My question was more related to how Url.Action() generates a url to match that route.)
jessegavin
A: 

You dont have to write new method for all condition. I mean you can do it with this;

eg. for NHibernate.

public IList<Blog> GetBlogs(int? day, int? month, int? year) {
    IQueryable<Blog> iQBlog = Session.Linq<Blog>();

    if (year.HasValue) {
        iQBlog = iQBlog.Where(b => b.Year == year);

        if (month.HasValue) {
            iQBlog = iQBlog.Where(b => b.Month == month);

            if (day.HasValue) {
                iQBlog = iQBlog.Where(b => b.Day == day);
            }
        }
    }

    return iQBlog.ToList();
}

ps. It's checking parameters step by step, year -> month -> day. If user doesnt set any parameters in querystring, it will return all blogs.

cem
That is actually what I am already doing. My question was more related to Url routing.
jessegavin
Yup i understood your question but already answered and s/he's doing it with new methods. I said it because maybe it will help you
cem