Building a photoblog with Nancy and Simple.Data Part 7: The archives

This is the seventh part in my Building a photoblog with Nancy and Simple.Data series:

  1. Setting up the project
  2. Defining the routes
  3. Rendering some views
  4. Adding the database
  5. Updating Simple.Data
  6. Adding comments
  7. The archives

Once again, it’s been a while since the last post. The people behind Nancy and Simple.Data get a lot more work done than I can, so we’ll start this post by updating Nancy and Simple.Data again. Once that is done we tackle the archives pages. With that, we’ll complete the front-end!

Updating Simple.Data

Just like before, open the Package Manager Console and enter Update-Package Simple.Data.SqlCompact40. This will update all three Simple.Data packages to their latest version. At the moment that I’m writing this, that’s version 0.8.2.

Updating Nancy

Remember how we switched to the NuGet versions of the Nancy packages in the previous post? Well, because of that updating Nancy is as easy as updating Simple.Data. Just enter Update-Package Nancy.Hosting.Aspnet and Update-Package Nancy.Viewengine.Razor. This will update all of the Nancy packages to their latest version. At the moment that I’m writing this, that’s version 0.7.1.

There’s one little thing we’ll have to change in our code to ensure that everything works. Open the PhotoBootstrapper class and change the line about the CookieBasedSessions to this:

public class PhotoBootstrapper : DefaultNancyBootstrapper
{
	protected override void InitialiseInternal(TinyIoC.TinyIoCContainer container)
	{
		base.InitialiseInternal(container);
		Nancy.Session.CookieBasedSessions.Enable(
            this,
            Nancy.Cryptography.CryptographyConfiguration.Default);
	}
}

Adding the archives pages

As you might remember from the second post, we have defined three routes for the archives. We’ll deal with them one by one.

The general archives

This page will show a list of all years and months in which photo’s have been published. For example:

Let’s first add a view model for the general archives. Add a class called GeneralArchives to the Models directly and add the following code:

public class GeneralArchives
{
	public List<YearInfo> Years { get; set; }

	public GeneralArchives()
	{
		Years = new List<YearInfo>();
	}
}

public class YearInfo
{
	public int Year { get; set; }
	public int NumberOfPhotos { get; set; }
	public List<MonthInfo> Months { get; set; }

	public YearInfo()
	{
		Months = new List<MonthInfo>();
	}
}

public class MonthInfo
{
	public int Month { get; set; }
	public string MonthName { get; set; }
	public int NumberOfPhotos { get; set; }
}

Now that we have a view model to pass to our view, we can add code to the Get[""] route in the ArchivesModule:

Get[""] = parameters =>
{
	List<DateTime> allDates = DB.Photos
                                .Query()
                                .Select(DB.Photos.DatePublished)
                                .Where(DB.Photos.Published == true)
                                .OrderByDatePublishedDescending()
                                .ToScalarList<DateTime>();

	var model = new GeneralArchives
					{
						Years = (from date in allDates
								 group date by date.Year
								 into yearGroup
								 select new YearInfo
											{
												Year = yearGroup.Key,
												NumberOfPhotos = yearGroup.Count(),
												Months = (from yearDate in yearGroup
														  group yearDate by yearDate.Month
														  into monthGroup
														  select new MonthInfo
																	 {
																		 Month = monthGroup.Key,
																		 MonthName = monthGroup.First().ToString("MMMM"),
																		 NumberOfPhotos = monthGroup.Count()
																	 }).ToList()
											}).ToList()
					};

	return View["general-archives", model];
};

That looks like a lot of code, but there are actually only three things happening. On lines 3 to 8, we ask Simple.Data for a list of all dates on which photo’s have been published. On lines 10 to 29 we use a LINQ operation to transform that list of dates into an instance of our GeneralArchives class filled with nested lists of years and months. Finally, on line 31, we return a view with the GeneralArchives instance as the model.

Speaking of the view, let’s add that one too! Add a file called general-archives.cshtml to the Views directory and add the following code:

<!doctype html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

    <title>Archives</title>
    <link rel="Stylesheet" type="text/css" href="/Content/Css/reset.css" />
    <link rel="Stylesheet" type="text/css" href="/Content/Css/photoblog.css" />

    <script type="text/javascript" src="/Content/Scripts/modernizr-1.7.min.js"></script>
</head>
<body>
    <header><a href="/">MyPhotoBlog</a></header>
    <div id="main">
        <h1>Archives</h1>
        <div id="photo">
            <div>
                <ul class="years">
                @foreach (var yearInfo in Model.Years)
                {
                    <li>
                        <a href="/archives/@yearInfo.Year">@yearInfo.Year</a> (@yearInfo.NumberOfPhotos)
                        <ul class="months">
                            @foreach (var monthInfo in yearInfo.Months)
                            {
                                <li><a href="/archives/@yearInfo.Year/@monthInfo.Month">@monthInfo.MonthName</a> (@monthInfo.NumberOfPhotos)</li>
                            }
                        </ul>
                    </li>
                }
                </ul>
            </div>
        </div>
    </div>
    <footer>MyPhotoBlog is built with <a href="https://github.com/NancyFX/Nancy">Nancy</a> and <a href="https://github.com/markrendle/Simple.Data">Simple.Data</a>.</footer>

    <script type="text/javascript" src="/Content/Scripts/jquery-1.5.2.min.js"></script>
</body>
</html>

On line 20, we start to loop over the YearInfo items in the Years property of our view model. On line 23 we create a link to the yearly archives page for the current YearInfo item and on line 25 we start to loop over the MonthInfo items in the Months property of the current YearInfo item. Finally, on line 27, we also add a link to the monthly archives page for the current MonthInfo item.

The yearly archives

This page will show a list of all photo’s that have been published in the specified year. Building this page is very similar to the general archives. We start with the view model. Add a YearlyArchives class to the Models directory and add this code:

public class YearlyArchives
{
	public List<Photo> Photos { get; set; }
	public int Year { get; set; }

	public YearlyArchives()
	{
		Photos = new List<Photo>();
	}
}

Now we can modify the Get[@"/(?<year>19[0-9]{2}|2[0-9]{3})"] route in the ArchivesModule:

Get[@"/(?<year>19[0-9]{2}|2[0-9]{3})"] = parameters =>
{
	int year = Convert.ToInt32((string)parameters.year);
	DateTime start = new DateTime(year, 1, 1);
	DateTime end = start.AddYears(1);

	List<Photo> photos = DB.Photos
                           .FindAllByDatePublishedAndPublished(start.ToString("yyyy-MM-dd").to(end.ToString("yyyy-MM-dd")), true)
                           .OrderByDatePublished()
                           .ToList<Photo>();

	var model = new YearlyArchives
					{
						Photos = photos,
						Year = year
					};

	return View["yearly-archives", model];
};

As far as I know, it’s not possible to filter records based on a part of the date with Simple.Data. That’s why we just get all records between two dates. To do this, we can use the following Simple.Data notation: FindByDate("2011-01-01".to("2011-12-31")), as you can see on line 8.

Once we have our list of photo’s, we can create an instance of our YearlyArchives view model class and pass it on to a view.

For the view, add a file called yearly-archives.cshtml to the Views directory with the following code:

<!doctype html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

    <title>Archives for @Model.Year</title>
    <link rel="Stylesheet" type="text/css" href="/Content/Css/reset.css" />
    <link rel="Stylesheet" type="text/css" href="/Content/Css/photoblog.css" />

    <script type="text/javascript" src="/Content/Scripts/modernizr-1.7.min.js"></script>
</head>
<body>
    <header><a href="/">MyPhotoBlog</a></header>
    <div id="main">
        <h1>Archives for @Model.Year</h1>
        <div id="photo">
            <div>
                <ul class="photolist">
                @foreach (var photo in Model.Photos)
                {
                    <li><a href="/photo/@photo.Slug">@photo.Name</a> <span class="photodate">(@photo.DatePublished.ToLongDateString())</span></li>
                }
                </ul>
            </div>
        </div>
        <div id="backtoarchives"><a href="/archives">Back to the archives</a></div>
    </div>
    <footer>MyPhotoBlog is built with <a href="https://github.com/NancyFX/Nancy">Nancy</a> and <a href="https://github.com/markrendle/Simple.Data">Simple.Data</a>.</footer>

    <script type="text/javascript" src="/Content/Scripts/jquery-1.5.2.min.js"></script>
</body>
</html>

On lines 7 and 16 we show the correct title for the yearly archive, based on the chosen year. On lines 20 to 23, we loop through the list of photo’s and display a link to the detail page of the photo.

The monthly archives

The monthly archives are very similar to the yearly ones, so I’ll just show the relevant code.

The MonthlyArchives view model class in the Models directory:

public class MonthlyArchives
{
	public string Month { get; set; }
	public List<Photo> Photos { get; set; }

	public MonthlyArchives()
	{
		Photos = new List<Photo>();
	}
}

The Get[@"/(?<year>19[0-9]{2}|2[0-9]{3})/(?<month>[1-9]|1[012])"] route in the ArchivesModule:

Get[@"/(?<year>19[0-9]{2}|2[0-9]{3})/(?<month>[1-9]|1[012])"] = parameters =>
{
	int year = Convert.ToInt32((string)parameters.year);
	int month = Convert.ToInt32((string)parameters.month);
	DateTime start = new DateTime(year, month, 1);
	DateTime end = start.AddMonths(1);

	List<Photo> photos = DB.Photos
                           .FindAllByDatePublishedAndPublished(start.ToString("yyyy-MM-dd").to(end.ToString("yyyy-MM-dd")), true)
                           .OrderByDatePublished()
                           .ToList<Photo>();

	var model = new MonthlyArchives
	{
		Photos = photos,
		Month = start.ToString("MMMM yyyy")
	};

	return View["monthly-archives", model];
};

The monthly-archives.cshtml view in the Views directory:

<!doctype html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

    <title>Archives for @Model.Month</title>
    <link rel="Stylesheet" type="text/css" href="/Content/Css/reset.css" />
    <link rel="Stylesheet" type="text/css" href="/Content/Css/photoblog.css" />

    <script type="text/javascript" src="/Content/Scripts/modernizr-1.7.min.js"></script>
</head>
<body>
    <header><a href="/">MyPhotoBlog</a></header>
    <div id="main">
        <h1>Archives for @Model.Month</h1>
        <div id="photo">
            <div>
                <ul class="photolist">
                @foreach (var photo in Model.Photos)
                {
                    <li><a href="/photo/@photo.Slug">@photo.Name</a> <span class="photodate">(@photo.DatePublished.ToLongDateString())</span></li>
                }
                </ul>
            </div>
        </div>
        <div id="backtoarchives"><a href="/archives">Back to the archives</a></div>
    </div>
    <footer>MyPhotoBlog is built with <a href="https://github.com/NancyFX/Nancy">Nancy</a> and <a href="https://github.com/markrendle/Simple.Data">Simple.Data</a>.</footer>

    <script type="text/javascript" src="/Content/Scripts/jquery-1.5.2.min.js"></script>
</body>
</html>

That’s it!

Whew. Three view models, three routes and three views. I’ve earned myself a drink!

As usual, the code is available on GitHub.

This entry was posted in Development, MyPhotoBlog and tagged , . Bookmark the permalink.

One Response to Building a photoblog with Nancy and Simple.Data Part 7: The archives

  1. Mark Rendle says:

    As of 0.8.2, Simple.Data supports the use of arbitrary functions on columns. This means that you can specify any database method which takes the column as its first parameter; in this instance you could say DB.Photos.FindAll(DB.Photos.PublishedDate.Year() == start.Year), which would translate into “WHERE Year(Photos.PublishedDate) = @p1″. Of course, I’m not sure that’s valid syntax for T-SQL; it’s DatePart or something, and I’ve not come up with a way to pass constant names in yet.

    In this instance, though, it is probably better to use the Range approach, particularly if DatePublished is indexed.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Please leave these two fields as-is: