I would like to create custom slugs for pages in my CMS, so users can create their own SEO-urls (like Wordpress).
I used to do this in Ruby on Rails and PHP framewor
Even if your route registration code works as is, the problem will be that the routes are registered statically only on startup. What happens when a new post is added - would you have to restart the app pool?
You could register a route that contains the SEO slug part of your URL, and then use the slug in a lookup.
RouteConfig.cs
routes.MapRoute(
name: "SeoSlugPageLookup",
url: "Page/{slug}",
defaults: new { controller = "Page",
action = "SlugLookup",
});
PageController.cs
public ActionResult SlugLookup (string slug)
{
// TODO: Check for null/empty slug here.
int? id = GetPageId (slug);
if (id != null) {
return View ("Show", new { id });
}
// TODO: The fallback should help the user by searching your site for the slug.
throw new HttpException (404, "NotFound");
}
private int? GetPageId (string slug)
{
int? id = GetPageIdFromCache (slug);
if (id == null) {
id = GetPageIdFromDatabase (slug);
if (id != null) {
SetPageIdInCache (slug, id);
}
}
return id;
}
private int? GetPageIdFromCache (string slug)
{
// There are many caching techniques for example:
// http://msdn.microsoft.com/en-us/library/dd287191.aspx
// http://alandjackson.wordpress.com/2012/04/17/key-based-cache-in-mvc3-5/
// Depending on how advanced you want your CMS to be,
// caching could be done in a service layer.
return slugToPageIdCache.ContainsKey (slug) ? slugToPageIdCache [slug] : null;
}
private int? SetPageIdInCache (string slug, int id)
{
return slugToPageIdCache.GetOrAdd (slug, id);
}
private int? GetPageIdFromDatabase (string slug)
{
using (CMSContext db = new CMSContext()) {
// Assumes unique slugs.
Page page = db.Pages.Where (p => p.Slug == requestContext.Url).SingleOrDefault ();
if (page != null) {
return page.Id;
}
}
return null;
}
public ActionResult Show (int id)
{
// Your existing implementation.
}
(FYI: Code not compiled nor tested - haven't got my dev environment available right now. Treat it as pseudocode ;)
This implementation will have one search for the slug per server restart. You could also pre-populate the key-value slug-to-id cache at startup, so all existing page lookups will be cheap.
I've edited my answer to give a more complete answer to your questions:
Answer to Question 1:
Registering routes is initialized on startup. (Perhaps also when the Application Pool
recycles, it's highly probable.)
I also think there is nothing wrong with your approach since it is occuring only once.
I do the same thing querying all supported languages from the database to register them as /TwoLetterISOLanguageName (/nl, /en, /de, etc.).
Answer to Question 2:
This should work passing a model:
Put it before the Default
route!
routes.MapRoute(
name: "Contact",
url: "contact/{action}",
defaults: new { controller = "Contact",
action = "Index",
MyModel = new MyModel { Name = "hello" } });
The ContactController:
public ActionResult Index(MyModel mymodel)
{
return Content(mymodel.Name);
}
The Model:
public class MyModel
{
public string Name { get; set; }
}