This is the second part in my Building a photoblog with Nancy and Simple.Data series:
- Setting up the project
- Defining the routes
- Rendering some views
- Adding the database
- Updating Simple.Data
- Adding comments
- The archives
In the previous post we created the Visual Studio solution, added the necessary references to Nancy and Simple.Data and tested two basic routes to see if Nancy was setup correctly. In this post we will decide what routes we will need in the application and we will configure our NancyModules accordingly. Let’s get started!
What routes do we need?
To answer that question, we first need to determine what kind of information our application needs to expose and how people should interact with it. Once we know that, we can map the requirements to a route and a method (POST or GET). To make things a bit easier, we will split up the requirements in two parts: front end and back end. The front end of the photoblog is visible for everyone while the back end is only visible for the logged in administrator, me.
Front end requirements
-
Visit the homepage to view the latest photo
Route: /
Method: GET -
Visit a specific photo with its comments
Route: /photo/{slug}
Method: GET -
Add a comment to a specific photo
Route: /photo/{slug}/addcomment
Method: POST -
Visit the general archives
Route: /archives
Method: GET -
Visit the archives for a certain year
Route: /archives/{year}
Method: GET -
Visit the archives for a certain month of a certain year
Route: /archives/{year}/{month}
Method: GET
Back end requirement
-
View the login form
Route: /admin
Method: GET -
Login on the system
Route: /admin/login
Method: POST -
View the list of photo’s
Route: /admin/photos
Method: GET -
Add a new photo
Route: /admin/photos/add
Method: GET and POST -
Edit a photo
Route: /admin/photos/edit/{slug}
Method: GET and POST -
View a list of comments
Route: /admin/comments
Method: GET -
Delete a comment
Route: /admin/comments/delete/{id}
Method: POST
Now we have that figured out, we can start organizing the routes into modules.
Creating the modules
I think it’s probably easier to split the routes into multiple modules. I think we will need five:
- One for the homepage
- One for the photo’s
- One for the archives
- One for the admin stuff
- A base class to provide common functionality to the other modules
The base class
For now, the base class doesn’t need to do anything special, but as soon as we start working with the database it will come in handy. Let’s create the base class first so all other modules are future proof.
Create a new directory in the root of the project and call it Modules. Add a new class called PhotoblogModule to that new directory. Make the class abstract, let it inherit from NancyModule and add the following code to the class:
public abstract class PhotoblogModule : NancyModule
{
public PhotoblogModule() : base()
{
}
public PhotoblogModule(string modulePath) : base(modulePath)
{
}
}
This gives us two constructors. We’ve already used the default parameterless constructor in the first post. The second constructor however is new. It accepts one parameter called modulePath and we’ll see what that does in just a moment.
The homepage module
Add another class to the Modules directory and call it RootModule. Let it inherit from the PhotoblogModule class we just made and the one route it should support:
public class RootModule : PhotoblogModule
{
public RootModule() : base()
{
Get["/"] = parameters =>
{
return "Homepage";
};
}
}
The photo module
Just like before, add a new class to the Modules directory, call it PhotoModule, let it inherit from PhotoblogModule as well and add the two routes it needs to take care of:
using Nancy;
public class PhotoModule : PhotoblogModule
{
public PhotoModule() : base("/photo")
{
Get["/{slug}"] = parameters =>
{
return String.Format("Photo '{0}'", parameters.slug);
};
Post["/{slug}/addcomment"] = parameters =>
{
string photoSlug = Convert.ToString(parameters.slug);
return Response.AsRedirect("/photo/" + photoSlug);
};
}
}
Did you see how we are calling base("/photo")? That’s the second constructor of our PhotoblogModule class. The modulePath parameter makes sure that all routes specified in the Get[] and Post[] indexers are relative to the modulePath. So when we add Get["/{slug}"], we are actually adding Get["/photo/{slug}"].
The archives module
Again, add a new class to the Modules directory, call it ArchivesModule, let it inherit from PhotoblogModule as well and add the routes it needs to handle:
public class ArchivesModule : PhotoblogModule
{
public ArchivesModule() : base("/archives")
{
Get[""] = parameters =>
{
return "All photo's of all years and months.";
};
Get["/{year}"] = parameters =>
{
return String.Format("All photo's of the year {0}",
parameters.year);
};
Get["/{year}/{month}"] = parameters =>
{
return String.Format("All photo's of month {0} of the year {1}",
parameters.month,
parameters.year);
};
}
}
See how we used the second constructor again to specify a modulePath?
The admin module
The last one! Add another class to the Modules directory, call it AdminModule, let it inherit from PhotoblogModule as well and add these routes:
public class AdminModule : PhotoblogModule
{
public AdminModule() : base("/admin")
{
Get[""] = parameters =>
{
return "Display the login form.";
};
Post["/login"] = parameters =>
{
// Perform validation, then redirect
return Response.AsRedirect("/admin/photos");
};
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["/comments"] = parameters =>
{
return "A list of all the comments.";
};
Post["/comments/delete/{id}"] = parameters =>
{
// Delete the comment, then redirect
return Response.AsRedirect("/admin/comments");
};
}
}
There. That was the last of our modules.
Wrapping things up
Now that we have our five modules in the Modules directory, we can delete the MainModule.cs we created in the first post since we won’t be needing it anymore. All modules and routing information are nicely grouped in the Modules directory. Our project now looks like the one on the right.
The changes to source code can be found on Github.
In the next post we’ll try to replace those temporarily returned strings with Razor views.
Pingback: Building a photoblog with NancyFX and Simple.Data Part 1: Setting up the project | Kristof Claes
Hi!
I just wanted to point out that the modulePath for the ArchivesModule sample is wrong, it still says ‘/photo’
It might also be useful for you to know that you can enter a regex for your route, it could be useful for you since you want to capture dates, so you could limit it to only capture digits for {year} etc. Here is an old gist that shows how to use it (normal regex capture groups) https://gist.github.com/834598
Yeah, I was in a bit of hurry when copy-pasting the code for the ArchiveModule apparently. It should be alright now
Thanks for the regex tip! That will definitely come in handy!
Just one comment – you probably want to break out the login route(s) (the one that shows the login page, and the one that takes the login) because, if you use the built in security, then it’s secured a the module level and you’ll end up with a secured login page
There’s a forms auth sample in the main project if you want to take a looksie.
A secured login page would be rather useless indeed
Thanks for the tip! I’m making this up as I go along. I’ll bump into a few more problems I guess, but it’s a good way to learn things.
Pingback: Building a photoblog with Nancy and Simple.Data Part 1: Setting up the project | Kristof Claes
Pingback: Building a photoblog with Nancy and Simple.Data Part 3: Rendering some views | Kristof Claes
Pingback: Building a photoblog with Nancy and Simple.Data Part 4: Adding the database | Kristof Claes
Pingback: Building a photoblog with Nancy and Simple.Data Part 5: Updating Simple.Data | Kristof Claes