Build your own CMS – Iteration 1: Data Model, Templates, Content Creation

January 29, 2011

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

This content management system will be built iteratively. Iteration 1 is far from a complete solution, but it is already fully functioning as a CMS. This post will discuss the current (simple!) database architecture and the concept of (straightforward!) templating. Then I’ll move on to how the admin site (dynamically!) provides a way to create and edit content pages.

The database

It doesn’t get any simpler than this. If you exclude the Languages table which is only there to support the future localization features of the CMS, we’re only working with two tables.

CMS database

ContentPages – this contains all content “page” records, which currently consists of the page’s URL alias, and the XML-based template name.

ContentPages table

ContentItems – this contains the actual content associated with a page. Each content record is for a specific language and contentid. The contentid is the identifier of a “content item” element in the XML template file. More on that below.

 ContentItems table

Languages – this should be self-explanatory. I’ll be discussing this in depth in future posts. For now just relax, everything is going to have a language id of “1” – English.

Template-driven content

XML-based templates determine the set of “content items” that defines a type of page. Below is the Product template:

<?xml version="1.0" encoding="utf-8" ?>
<template name="Product">
    <contentItems>
        <include template="_Includes\PageMaster.xml" />
        <content id="Title" type="text" isHtml="false"></content>
        <content id="Body" type="textarea" isHtml="true"></content>
        <content id="PrimaryImageUrl" type="text" isHtml="false"></content>
    </contentItems>
</template>

The id attributes define the unique id of the content item and this corresponds to the contentid column in the ContentItems database table. The other attributes are metadata that describes what type of content item it is. These are used in the CMS Administration application to render and validate the proper field type (when creating/editing a page), and by the client web site to render the content.

Templates support nesting, which provides the flexibility to define content items that may be used by multiple templates. The <include> element tells the system to pull in the content items from the included template.

The PageMaster template consists of a few globally used content items:

<?xml version="1.0" encoding="utf-8" ?>
<template name="PageMaster">
    <contentItems>
        <content id="PageTitle" type="text" isHtml="false"></content>
        <content id="MetaKeywords" type="text" isHtml="false"></content>
        <content id="MetaDescription" type="text" isHtml="false"></content>
    </contentItems>
</template>

This means a Product page consists of 6 total content elements: PageTitle, MetaKeywords, MetaDescription, Title, Body, and PrimaryImageUrl.

The CMS Administration Application

Oh yes, I brought a chart.

CMS architecture

This is mostly marketing BS – an attempt to fool you into thinking I know what I’m doing. Okay, I’m only half-kidding. The reality is that some of the lines between components here are blurred and, as this is just iteration 1, it will likely change. The basic idea is this:

(MVC App + Template Files + Data) * Awesome = CMS

Below is an example of the “Create Content Page” page. Select a template, select a language, and the Page Content items are loaded up and dynamically* a form gets rendered. Select a URL alias for the page, fill out any content fields, save – done!

Create Content Pag

*Rendering this form dynamically is a double-edged sword. It’s great because after coding this just once, end users can create and edit an infinite number of pages with an infinite variety of templates. However, with many fields this form could get a bit unwieldy. Users might have trouble finding the field that corresponds to the part of the rendered page they are attempting to edit. Therefore, I’m also going to build an override system where a developer can explicitly design and layout the fields of a particular template in any way they see fit. A double-edged sword of awesome.

I’m just going to post pseudo-code, as it would be a bit much to include in full. The form gets rendered in a spirit similar to Dynamic forms with ASP.NET MVC and XML. The Controller retrieves the template and passes it to the view, which in part might look something like this:

<div>
    <fieldset>
       <legend>Page Content</legend>

            <%foreach(var item in Model.Template.ContentItems) { %>
                <div class="editor-label">
                    <%=item.Id %>
                </div>
                <div class="editor-field">
                    <!-- hiding logic to select the proper input type/style 
			    based on attributes of ContentItem -->
                    <%=Html.TextBox(item.Id, item.value) %>
                </div>
            <%} %>

            <input type="hidden" name="templateName" value="<%=Model.Template.Name %>" />
            <input type="submit" name="submit" value="Create Page" />

    </fieldset>
</div>

The form is posted to a controller that does something along the lines of:

[HttpPost]
[ValidateInput(false)]
public ActionResult CreateNewPage(string alias, string templateName /*, form data */)
{
    var template = TemplateManager.GetTemplate(templateName);

    var contentItems = MapFormFieldsToTemplateAndCreateListOfContentItems();

    _repository.CreateContentItems(contentItems);

    return RedirectToAction("success");
}

Once this is successfully saved to the database, the data looks something like this:

image

The page edit mechanism works the same way, only with the added step of having to query the content for a given contentpageid. With the magic of the (not) patent-pending “Template Transformation Engine”, the data is wired up with the template into a nice moist ball of content goodness.

//get page by id
var contentPage = _repo.ContentPages.Query
    .Where(x => x.Id == theContentPageIdToEdit)
    .SingleOrDefault();

//get the template metadata by template name (xml file lookup)
var template = TemplateManager.GetTemplate(contentPage.TemplateName);

//loop through each item in template, find matching item within the page content
foreach (var templateContentItem in Template.ContentItems)
{
    var contentItem = contentPage.ContentItems
        .Where(c => c.ContentId == templateContentItem.Id)
        .SingleOrDefault();

    //templateContentItem & contentItem is everything we need to
    //render the hydrated and formatted form field for this piece of content
}

And this brings us to the next discussion: how the client website for our fictitious company, GlobalBeers, retrieves and renders this content on its pages. Stay tuned!

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