Building a photoblog with Nancy and Simple.Data Part 3: Rendering some views

This is the third 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

In the previous post we added some NancyModules to our project and defined some routes in them. To test the routes, we let them send a simple string back to the browser. In this post we will first make some small adjustments to our routes and modules based on feedback on the previous post. Next we will see how we can use Razor views to send a response back to the browser. Let’s get started!

Creating an AuthenticationModule

In a comment on the previous post, Steven Robbins (also know as @grumpydev on Twitter) explained that the built in security, which we will use, secures routes at the module level. When we secure the AdminModule class, the login page will also be secured. That means you would have to be logged in to log in. Obviously, we don’t want that :-) That’s why will put the login routes in their own module.

Just like we did in the previous post, we will create a new class in the Modules directory. Let’s call it AuthenticationModule. As you will know by know, the class should inherit from PhotoblogModule. Add these routes:

public class AuthenticationModule : PhotoblogModule
{
	public AuthenticationModule() : base()
	{
		Get["/login"] = parameters =>
		{
			return "Display the login form";
		};

		Post["/login"] = parameters =>
		{
			// Perform validation, then redirect
			return Response.AsRedirect("/admin/photos");
		};

		Post["/logout"] = parameters =>
		{
			// Logout and redirect
			return Response.AsRedirect("/login");
		};
	}
}

Updating the AdminModule

Now that we have put the login routes in the AuthenticationModule, we can remove them from the AdminModule. We also have to add a new route, one which I didn’t think about when writing the previous post. We have added routes to add and edit a photo, but there is no way to delete one. Let’s add that one. After removing the Get[""] and Post["/login"] and adding the Get["/photos/delete/{slug}"] and Post["/photos/delete/{slug}"] routes, our AdminModule looks like this:

public class AdminModule : PhotoblogModule
{
	public AdminModule() : base("/admin")
	{
		Get["/photos"] = parameters =>
		{
			return "A list of all the photo's.";
		};

		Get["/photos/add"] = parameters =>
		{
			return "Display the form to add a photo.";
		};

		Post["/photos/add"] = parameters =>
		{
			// Add the photo, then redirect
			string slug = "newPhoto";
			return Response.AsRedirect("/admin/photos/edit/" + slug);
		};

		Get["/photos/edit/{slug}"] = parameters =>
		{
			return String.Format("Display the form to edit a photo called '{0}'.",
				parameters.slug);
		};

		Post["/photos/edit/{slug}"] = parameters =>
		{
			// Edit the photo, then redirect
			string slug = Convert.ToString(parameters.slug);
			return Response.AsRedirect("/admin/photos/edit/" + slug);
		};

		Get["/photos/delete/{slug}"] = parameters =>
		{
			return String.Format("Are you sure you want to delete the photo called '{0}'?",
				parameters.slug);
		};

		Post["/photos/delete/{slug}"] = parameters =>
		{
			// Delete the photo, then redirect
			return Response.AsRedirect("/admin/photos");
		};

		Get["/comments"] = parameters =>
		{
			return "A list of all the comments.";
		};

		Post["/comments/delete/{id}"] = parameters =>
		{
			// Delete the comment, then redirect
			return Response.AsRedirect("/admin/comments");
		};
	}
}

Improving the ArchivesModule

As TheCodeJunkie (@TheCodeJunkie on Twitter), the main author of Nancy, suggests, we can use a regular expression in the route definitions to filter the archives by year and/or month. I came up with these:

public class ArchivesModule : PhotoblogModule
{
	public ArchivesModule() : base("/archives")
	{
		Get[""] = parameters =>
		{
			return "All photo's of all years and months.";
		};

		Get[@"/(?<year>19[0-9]{2}|2[0-9]{3})"] = parameters =>
		{
			return String.Format("All photo's of the year {0}",
				parameters.year);
		};

		Get[@"/(?<year>19[0-9]{2}|2[0-9]{3})/(?<month>0[1-9]|1[012])"] = parameters =>
		{
			return String.Format("All photo's of month {0} of the year {1}",
				parameters.month,
				parameters.year);
		};
	}
}

This makes sure that the routes only get called when the year parameter is a numerical value between 1900 and 2999. Maybe not ideal, but it does the trick. The month parameter has to be a numerical value between 1 and 12.

That’s it for the changes. On with the show!

Adding our first Razor view

At the moment of writing this post, Nancy expects all views to be located in a Views directory in the root of your application. The view location conventions will get updated in a few weeks, but for now we have to follow the current rules. So, let’s create a new directory called Views in the root of our application. Right click the directory and choose Add and New Item…. Select the HTML Page type, pick photodetail as the name and change the extension to cshtml.

Add the first view

Unfortunately, the Razor ViewEngine implementation in Nancy doesn’t support the use of Layouts (yet?) because the code that enables this isn’t located in the Razor assembly but in the System.Web.Mvc assembly, so each of our Razor files will have contain the complete markup for the page.

Add the following code to the newly created photodetail.cshtml file:

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

    <title>My First View</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>MyPhotoBlog</header>
    <div id="main">
        <div id="photo">
            <img src="" width="960" height="640" />
        </div>
        <div id="exif">
            <span class="type">Camera:</span> <span id="camera" class="value camera">Sony A200</span>
            <span class="type">Lens:</span> <span id="lens" class="value">Sony DT 50mm F1.8 SAM</span>
            <span class="type">Aperture:</span> <span id="aperture" class="value">f/4</span>
            <span class="type">Exposure:</span> <span id="exposure" class="value">1/250</span>
            <span class="type">ISO:</span> <span id="iso" class="value">200</span>
        </div>
        <nav>
            <a href="#" id="previous">« Previous</a>
            <a href="#" id="archives">Archives</a>
            <a href="#" id="next">Next »</a>
        </nav>
    </div>
    <footer>MyPhotoBlog is built with <a href="https://github.com/thecodejunkie/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>

Calling the view from a route

Now that we have created a view, we need to tell Nancy when to call it. Let’s make this view the homepage view. The root route (hehe) is located in our RootModule class. Open that file and adjust the Get["/"] route so that looks like this:

Get["/"] = parameters =>
{
    return View["photodetail"];
};

Using CSS and Javascript

As you might have noticed, the view we just created uses two CSS and two Javascript files. We’ll put all content files (CSS, Javascript and images) in a dedicated directory. Create a directory called Content in the root of your project and add two subdirectories in it: Css and Scripts. I won’t put the full source of the four files in this post, but if you want to follow along, you can download them here.

Testing the view

When you press CTRL+F5, you’ll see that the Nancy now sends our view to the browser. But it looks a bit dull doesn’t it?

What a dull view!

It looks like our CSS isn’t applied. How is that possible? Well. Nancy intercepts all requests. That means that Nancy will also answer requests for CSS or Javascript files and because it doesn’t find a suitable route in of our modules, Nancy returns a 404 Page not found error.

To solve this, we need to hack the Nancy.dll assembly with a hex editor and add some assembly code to override certain low level function calls. Luckily for you, that’s not true :-) All we need to do is give the Content directory its own Web.Config file and disable Nancy for that directory. So add a new Web.Config file to the Content directory (right click the directory, pick Add, New Item… and choose Web Configuration File) and add the following content to it:

<?xml version="1.0"?>
<configuration>
    <system.web>
        <httpHandlers>
            <remove verb="*" path="*"/>
        </httpHandlers>
    </system.web>

    <system.webServer>
        <handlers>
            <remove name="Nancy" />
        </handlers>
    </system.webServer>
</configuration>

Press CTRL+F5 again (or just refresh the browser if it’s still open) and behold! The page still looks a bit dull, but a little less so :-)

A little less dull view!

Wrapping things up

The current project structureIn this post we have made a few adjustments to the routes and modules we created in the previous post. We have added our first view and instructed Nancy to return it when calling a certain route. We’ve also enabled the usage of CSS and Javascript files. We have added a Views and Content directory. Our project structure now looks like the one on the right.

The changes to source code can be found on Github.

In the next post we’ll see how we can replace the static content of the view with something more dynamic by letting Nancy pass in a viewmodel and maybe we’ll finally get started with Simple.Data.

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

8 Responses to Building a photoblog with Nancy and Simple.Data Part 3: Rendering some views

  1. Pingback: Building a photoblog with Nancy and Simple.Data Part 2: Defining the routes | Kristof Claes

  2. Hey

    You could make your delete routes use proper HTTP, by register them with Delete[..] routes, meaning you need to call the route with a HTTP DELETE call. Just a thought.

    For static contents we are working on a beforerequest hook (most likely in the form of a Nuget) that makes Nancy serve static contents without reaching a module. A sample of how you can do this can be found in the NerdBeers source code (https://github.com/ToJans/NerdBeers/blob/master/src/Org.NerdBeers/Org.NerdBeers.Web/Bootstrapper.cs) .. and you could easily move it out to a static method that you do soemthing like

    StaticFileHandler.Register(this, “Content”, new Dictionary
    {
    { “jpg”, “image/jpeg” },
    { “png”, “image/png” },
    { “gif”, “image/gif” },
    { “css”, “text/css” },
    { “js”, “text/javascript” }
    });

    That’s more or less how we plan on cleaning the implementation up before packaging it.

    With regards to Razor.. you are correct. Microsoft, for some odd reason, thought that it would be a good idea to put massive parts of the Razor Engine functionality in the MVC/WebPages projects and just stick the parser in System.Web.Razor.dll

    We are going to start working on the view engine story for Nancy sometime next week where we will try to address as many cross-cutting concerns as possible so all view engines can use the same supporting code. Keep an eye out on the user group for more on that.

    Keep up the good work!

  3. Kristof says:

    Thanks for the feedback.

    I noticed that the NerdBeers project indeed uses a beforerequest hook, but I got confused because there also is a Web.Config in the NerdBeers Content directory. So I wasn’t sure which way was best. The way you suggest looks nicer, so I’ll give that a try. Thanks!

  4. Pingback: Building a photoblog with Nancy and Simple.Data Part 1: Setting up the project | Kristof Claes

  5. paul says:

    Nice articles, Kristof. Looking forward to the next one!

    @TheCodeJunkie, if you register w/ Delete, is there some way to map downlevel forms to that, which only natively support GET/POST? Will it respect some form value or querystring param that tells it to route as Delete like other frameworks (Rails, MVC) do?

  6. @Paul yes and no – for ASP.Net hosting you can add a form field named “_method” and it will fake a request of a different method. We should really bring this logic somewhere else though, and only accept the override on POST requests, rather than POST and GET. I’ll log an issue on Github, but if you’re using ASP.Net it will work as is for you.

  7. Andreas HÃ¥kansson (TheCodeJunkie) says:

    FYI, the issue was logged https://github.com/thecodejunkie/Nancy/pull/125 and is targeted for the 0.6.0 release (will be in the repo as soon as it’s fixed though so keep an eye out)

  8. Pingback: Building a photoblog with Nancy and Simple.Data Part 4: Adding the database | Kristof Claes