Trouble using Url.Action() when trying to create friendly & "hackable" urls - asp.net-mvc

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:
http://abc.com/blog/2010 -> shows all
posts in 2010
http://abc.com/blog/2010/01 -> shows
all posts in January 2010
http://abc.com/blog/2010/01/25 ->
shows all posts in January 25th, 2010
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?

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.

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

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.

Related

date_select save higher day of the month available

I have a simple datetime attribute to pick a date like this on the views
= f.date_select :period_end_at, default: { day: 31 }
It defaults to last day of the month as the example. The problem is that if month selected is "June" that has 30 days, since there is no '31' day for June, it will save the object as day 1 instead of day 30.
Is there an easy way to always save to the highest day of the month if the value provided is above all available for that moonth?
Not sure if it could be shortened, but this should work (if I understood your question correctly):
= f.date_select :period_end_at, default: { day: Time.days_in_month(Time.now.month) }
Take a look at this js snippet, it works well for me with Rails 4.2.0
<script>
$(function(){
railsMonthDates();
$("select[id*=_2i], select[id*=_1i]").change( railsMonthDates );
});
function railsMonthDates() {
$("select[id*=_2i]").each(function(){
$monthSelect = $(this);
$daySelect = $(this).siblings("select[id*=_3i]");
$yearSelect = $(this).siblings("select[id*=_1i]");
var year = parseInt($yearSelect.val());
var month = parseInt($monthSelect.val());
var days = new Date(year, month, 0).getDate();
var selectedDay = $daySelect.val()
$daySelect.html('');
for(var i=1; i<=days; i++) {
$daySelect.append('<option value="'+i+'">'+i+'</option>');
}
$daySelect.val(selectedDay);
});
}
</script>
Simply paste it into the partial which has the form.
Pay attention, it match every element which has id*=_1i, id*=_2i or id*=_3i, so if you have more f.date_select you need to specify a better matcher.

ASP.NET MVC calculate week

I have a ASP.NET MVC 4 project with EF. In my (create) view I want to display beside an EditorFor for a date the week number of the current year.
I have a helper:
#model Helios.Models.tablename
#helper Week(DateTime dt)
{
CultureInfo ciCurr = CultureInfo.CurrentCulture;
int weekNum = ciCurr.Calendar.GetWeekOfYear(dt, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
#weekNum
}
...
<div class="editor-field">
#Html.TextBoxFor(model => model.date_birth, new {onchange="?"}) #Week(date_birth?)
</div>
I'm not sure if this can be acomplished in Razor, but I need to update the week number if I change the date
Q : How can I display the week no. beside the datepicker and update it if the date is changed ?
Here is a post on that deals with calculating the week number.
How can I calculate/find the week-number of a given date?
But it all depends on if you plan to set it on the server or if the client is to be able to change date. If the client can change the date then you would need javascript instead.
function(d) {
var day = d.getDay();
if(day == 0) day = 7;
d.setDate(d.getDate() + (4 - day));
var year = d.getFullYear();
var ZBDoCY = Math.floor((d.getTime() - new Date(year, 0, 1, -6)) / 86400000);
return 1 + Math.floor(ZBDoCY / 7);
}
From: http://jquery142.blogspot.se/2010/04/how-to-get-week-number-for-date-in.html

How do I get only unique month and year combinations?

Using VBNET, MVC 3 and Entity Framework to write my first mvc application - a single user blog application. I am trying to create an archive sidebar that will render something like October 2011 and when the user clicks they will see all posts in october. Right now all my attempts show either duplicate dates - if there are 3 posts in october then i see october 2011 3 times or i only get back one month year combo say oct 2011.
Using groupby with firstordefault i only get back one month yaear combo.
posts = _rdsqlconn.Posts.Where(Function(p) p.PostIsPublished = True).GroupBy(Function(d) d.PostDatePublished).FirstOrDefault
How can i get back unique month year combos with EF?
Additional info
I have that function in my repository. I want to pull the month and year pairs so that i have only one pair for say ocotober even if there are 3 posts in october.
In the repository:
Public Function SelectPostsByDate() As IEnumerable(Of Entities.Post) Implements Interfaces.IPostRepository.SelectPostsByDate
Using _rdsqlconn As New RDSQLConn
Dim posts
posts = _rdsqlconn.Posts.Where(Function(p) p.PostIsPublished = True).GroupBy(Function(p) New With {p.PostDateCreated.Year, p.PostDateCreated.Month}).Select(Function(g) g.Key)
'posts = _rdsqlconn.Posts.Where(Function(p) p.PostIsPublished = True).GroupBy(Function(p) New With {p.PostDatePublished.Value.Year, p.PostDatePublished.Value.Month})
Return posts
End Using
End Function
In my controller i have
Function DateViewPartial() As PartialViewResult
Return PartialView(_postRepository.SelectPostsByDate)
End Function
My partial view has:
#ModelType IEnumerable (of RiderDesignMvcBlog.Core.Entities.Post)
<hr />
<ul style="list-style: none; margin-left:-35px;">
#For Each item In Model
#<li> #Html.ActionLink(item.PostDatePublished.Value.ToString("Y"), "Archives", "Blog", New With {.year = item.PostDatePublished.Value.Year, .month = item.PostDatePublished.Value.Month}, Nothing)</li>
Next
</ul>
In _Layout.vbhtml i call the partial view to render in the sidebar:
<h3>Posts by Date</h3>
#code
Html.RenderAction("DateViewPartial", "Blog")
End Code
I would try this (in C#):
var yearMonths = _rdsqlconn.Posts
.Where(p => p.PostIsPublished)
.GroupBy(p => new { p.PostDatePublished.Year, p.PostDatePublished.Month })
.Select(a => a.Key)
.ToList();
It gives you a list of anonymous objects. Each object has a Year and a Month property - for instance yearMonths[0].Year and yearMonths[0].Month, etc. By applying the Select you actually throw away the elements in each group and you get only a list of group keys (year and month).
Edit
I think, for your purpose the best way is to introduce a "ViewModel" for your sidebar partial view. The ViewModel would describe the year and month group, for instance:
public class ArchiveMonthViewModel
{
public int Year { get; set; }
public int Month { get; set; }
}
Then you don't group with an anonymous type but use this ViewModel type:
var archiveViewModels = _rdsqlconn.Posts
.Where(p => p.PostIsPublished)
.GroupBy(p => new ArchiveMonthViewModel
{
Year = p.PostDatePublished.Year,
Month = p.PostDatePublished.Month
})
.Select(a => a.Key)
.ToList();
archiveViewModels is now a named type: List<ArchiveMonthViewModel> which you can return from your method:
public IEnumerable<ArchiveMonthViewModel> SelectPostsByDate()
{
// code above ...
return archiveViewModels;
}
Now your partial view should be based on a model of type IEnumerable<ArchiveMonthViewModel> (and not IEnumerable<Post>). In your foreach loop (#For Each item In Model) you pull out the ArchiveMonthViewModel elements which are the item in the loop now and then create the action link using item.Year and item.Month.
(Hopefully you can translate this sketch into VB.)
In VB:
Dim groupedPosts = _rdsqlcon.Posts.
Where(Function(p) p.PostIsPublished = True).
GroupBy(Function(p) New With {p.PostDatePublished.Year, p.PostDatePublished.Month }).
Select(g => g.Key)
This just returns the unique Years and Months. If you want to include the Posts for each, try the following:
Dim groupedPosts = _rdsqlcon.Posts.
Where(Function(p) p.PostIsPublished = True).
GroupBy(Function(p) New With {p.PostDatePublished.Year, p.PostDatePublished.Month
From there, you can show the year/month groupings and the associated posts for each:
For Each group In groupedPosts
Console.WriteLine(group.Key.Year & "-" & group.Key.Month)
For Each post In group
Console.WriteLine(post.PostDatePublished)
Next
Next

Very odd event behaviour for onChange on Telerik MVC DatePicker

To the question started, my code (I'll try to only include relevant portions to start), starting with my script:
function RaceDate_onChange() {
var pickedDate = $(this).data('tDatePicker').value();
var month = pickedDate.getMonth() + 1;
$.get("/RaceCard/Details?year=" + pickedDate.getFullYear() + "&month=" + month + "&day=" + pickedDate.getDate());
}
Then my markup:
#Html.Telerik().DatePickerFor(model => model.RaceDate).ClientEvents(events => events.OnChange("RaceDate_onChange"))
And finally a bit of the receiving action:
[HttpGet]
public ActionResult Details(int year, int month, int day)
{
var viewModel = new RaceCardModel {Metadata = DetailModelMetadata.Display, RaceDate = new DateTime(year, month, day)};
I'm trying to get the selection of a new date to trigger a GET, to refresh the page without submitting a form. This works fine, except for this problem:
In GET requests to the Details action, the day value is always one day behind the DatePicker. E.g. The first value is set from a view model property, when the view is rendered, say 3. I then click on 14 and hit my breakpoint in the action method. The day value is 3. When I click on 29 and hit the breakpoint, the day value is 14.
Besides asking what is wrong, I'll take a liberty and ask if there is a better way that is no more complicated. I am fairly novice and would rather deliver working code that needs revision than get bogged down in tangents and details.
Try using e.value instead as shown in the client-side events example. You are probably using an older version where the value() method returned the previous value during the OnChange event.
UPDATE:
"e.value" means the value field of the OnChange arguments:
function onChange(e) {
var date = e.value; // instead of datePicker.value()
}
As far as the 1 month difference you are getting, that's normal, and it is how the getMonth() method works in javascript on a Date instance:
The value returned by getMonth is an
integer between 0 and 11. 0
corresponds to January, 1 to February,
and so on.
So adding +1 is the correct way to cope with the situation, exactly as you did.
Just a little remark about your AJAX call: never hardcode urls. Always use url helpers when dealing with urls:
var year = pickedDate.getFullYear();
var month = pickedDate.getMonth() + 1;
var day = pickedDate.getDate();
var url = '#Url.Action("Details", "RaceCard")';
$.get(url, { year: year, month: month, day: day }, function(result) {
// process the results of the AJAX call
});

ASP.NET MVC Routing Help Required

I'm creating a online log viewer application which reads logs generated by many applications into a single common database. Log types are error, fatal, debug and I use all to represent all the logs.
I've a controller named AppsController which should server Views for the following requests, where "bi-reports" is one of the many application names we have.
/apps/bi-reports/
/apps/bi-reports/all
/apps/bi-reports/error/
/apps/bi-reports/2011/04/
/apps/bi-reports/2011/04/all
/apps/bi-reports/2011/error
/apps/bi-reports/2011/04/error
/apps/bi-reports/all/last-hundred
/apps/bi-reports/all/most-hundred
/apps/bi-reports/2011/last-hundred
/apps/bi-reports/2011/04/all/last-hundred
How should I configure routes set parameters in Action methods of Controller to get this working?
This is rough idea of your routing definition. I can see that you basically have three types of routes:
routes.MapRoute(
"IrrelevantDates",
"{controller}/{application}/{type}/{range}",
// defaults
new {
controller = "Apps",
action = "UnboundReport",
type = "all",
range = "no-limit"
},
// constraints
new {
type = "apps|error"
}
);
routes.MapRoute(
"RelevantYearOnly",
"{controller}/{application}/{year}/{type}/{range}",
// defaults
new {
controller = "Apps",
action = "YearlyReport",
type = "all",
range = "no-limit"
},
// constraints
new {
year = "19\d{2}|2[01]\d{2}",
type = "apps|error"
}
);
routes.MapRoute(
"RelevantYearAndMonth",
"{controller}/{application}/{year}/{month}/{type}/{range}",
// defaults
new {
controller = "Apps",
action = "MonthlyReport",
type = "all",
range = "no-limit"
},
// constraints
new {
year = "19\d{2}|2[01]\d{2}",
month = "0[1-9]|1[0-2]",
type = "apps|error"
}
);
I've set year constraint to match years between 1900 and 2199, and months so they actually have to specify a valid month 01 to 12.
If you have any additional controllers you will have to define a default route as well and put controller constraint on this one or make controller name static (when just one applies).
I would do something along these lines
''# note, this is untested VB and might need some tweaking.
routes.MapRouteLowercase("Base", "",
New With {.controller = "Home",
.action = "Index",
.year = UrlParameter.Optional,
.paging = UrlParameter.Optional},
New With {.year = "[0-9]*"})
Then your controller would have something like
Function Index(ByVal paging As String, ByVal year As Integer?) As ActionResult
''# do your pre-processing for paging and year.
Return View()
End Function

Resources