URL Aliasing: serving data-driven pages with custom URLs

January 28, 2011

This is the third post in a series documenting my development and implementation of a content management system (CMS) built on the ASP.NET MVC Platform.

URL aliasing, or URL rewriting, is a core feature of this application. Content creators need the ability to create new pages with a URL of their choice and it just needs to work. We can’t have the developer involved and managing a complex and always changing set of page URLs.

Thus, the URL of CMS content pages will come from the database like the rest of the content.

Initial data model for the “ContentPages” table

ContentPages table

This is plain ole vanilla ASP.NET URL rewriting with an HttpModule. Other than the fact we’re also managing the hand-off of the actual CMS page content object at the same time, the implementation is not unique.

URL Aliasing module flow

Aliasing with HttpContext.RewritePath()

In order for the application to serve up pages with arbitrary, data-driven URLs, we need to detect them and rewrite the request path to a generic Controller Action that can serve up the page.

I’ve done this with an HttpModule on BeginRequest:

void context_BeginRequest(object sender, EventArgs e)
{
    var app = sender as HttpApplication;
    if (app == null) return;

    var repo = ObjectFactory.GetInstance<ICmsRepository>();

    //try to get a CMS content page with alias matching current request
    var contentPage = repo.ContentPages.Query
        .Where(x => x.Alias == app.Request.FilePath)
        .SingleOrDefault();

    //if not found, request isn't for a CMS page. abort
    if (contentPage == null) return; 

    //request is for a CMS page. Add content object to request
    //and execute generic Controller Action
    app.Context.Items.Add("contentPage", contentPage);
    app.Context.RewritePath("~/Cms/ContentPage");
}

This module is querying the database for a CMS page record that has claimed ownership of the requested URL. If no record is found, the module returns (having done nothing) and the request falls down the pipeline, where a native ASP.NET MVC Controller Action will handle the request (or something else will happen, or a 404, etc).

If a record is found, it means we’re dealing with a CMS page. I’m adding the contentPage object to the request Context.Items collection so it can be retrieved later, and then calling RewritePath(), telling ASP.NET to instead execute the ContentPage Action of my Cms Controller.

public ActionResult ContentPage()
{
    var contentPage = HttpContext.Items["contentPage"] as ContentPage;
    if (contentPage == null) 
        throw new InvalidOperationException("Content Page missing.");

    return View(contentPage.TemplateName, contentPage);
}

Above is the all-purpose ContentPage Controller Action that will execute every CMS page request. All it does at its core is pull out the contentPage object from the Context.Items collection (where the HttpModule put it), and renders the view.

By convention, all page templates must have a view page of the same name, so the appropriate view is rendered.

blog comments powered by Disqus

About Kurt

I'm a senior consultant at Headspring in Austin, TX. My passion is creating web-based applications that are well crafted and solve real problems for real people. Want to know more? Check out my about page.

. @LipGlosserie setting up for Renegade Austin craft fair http://t.co/7X4WBVQb 15 hours ago