Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Lean and Mean Blogging Revisited

0.00/5 (No votes)
4 Jan 2008 1  
An alternative approach to blogging engines
BlogEntry02.png

Introduction

Preamble

First of all, before I even touch anything remotely technical, I�d like to say two things:

  1. A very big thank you to Marc Clifton (here too). His articles A Lean and Mean Blog Engine, and A Lean and Mean Blog Engine, part II on a lean and mean blogging engine are the inspiration for this article as an article.
  2. This is my first article, as such, I'm not asking you to be �gentle�, I'm telling you to take it apart, shred it to pieces, chew it and spit it back out. The more critical you are, the happier I'll be, because this way my next article will be that much better.

P.S.. I mention Marc Clifton many times in this article. I'm not idolizing him, but let it be known I totally respect the guy�s coding skillz. Yes, skillz.

Motivation

Several forces moved me into writing this article. Marc�s articles were a part of it. I like to do things, even if they are thought to be reinventing the wheel because I prove to myself that I can do it, and also I enjoy beating my own path to the goal as that makes me a better Engineer. And since my website has long been overdue, I took this as a double opportunity to both develop my website and build ABE.
Ok, now let�s get cracking and introduce you to ABE and talk shop.

ABE - Using the Code

ABE stands for �Another Blogging Engine�. As Developers and Engineers, we know that there are several correct routes to a defined goal. Choosing which way to go could be due to technical limitations, constraints, personal choice or coding style. That said, and though there are several blogging engines out there [Subtext, DotText, DasBlog and more], Marc chose to write his own for his reasons, and I share many of those reasons.

Blog Entry Requirements

With lessons learned from studying other Blogging Engines, I would list the features of blogs as follows:

  • Titles & Subtitles
  • The entry itself
  • The date the blog was written
  • The tags that categorize the blog entry
  • The ability to add comments
  • Permalinks so that visitors can both reference the blog entry and so that they can navigate straight to the blog entry instead of plowing through hundreds
  • Archives to reach older blogs

There are other things which make up a more complete blog such as friendly URLs and TrackBacks/PingBacks/LinkBacks. But that is beyond the scope of ABE at this stage. In fact, I opted to forgo the ability for readers to comment for now. I do want people to be able to comment on my Blog Entries, but all in good time.

Database

Before I get to the markup and code of the article, I'll post the diagram of the database that I used:

DbDiag.PNG
Figure 1: Database Diagram.

The backend is SQL Server 2005. This configuration ought to work with any version of SQL2k5. As you can see, it�s a very simple database. It�s all that is needed at the moment. In fact, it�s more than what is needed as there�s the foundation for comments that I haven't included in this version. Don't worry, the follow up article will be discussing comments, log ins, captcha and RSS feeds (although the feed is up and running).

UI

Now that we've narrowed down our requirements to the bare essentials of what makes a blog, and since I'm not very artistic, I went for the following look:

BlogEntry01.png
Figure 2: Blog Entry Look and Feel.

I also gave the alternate a slightly different look with the background a pleasant shade of grey.

Blog Entry Control

The architecture of the Blog Entry: I'm not going to be pointing out the Title or the Subtitle or the entry itself, those are obvious, but you will see that in the lower left corner we have two elements worthy of note. The category tags and the permalink address. On the lower right hand we have the date in long format so as not to confuse the format in use. I implemented the Blog Entry (as I named it) as a User Control with the markup as:

<%@ control language="C#" autoeventwireup="true" codefile="blogEntry.ascx.cs"
                          inherits="controls_blogEntry" enableviewstate="true" %>
<asp:table runat="server" width="100%" id="blogEntryTable">
<asp:TableRow runat="server" CssClass="blogEntryTitleRow">
    <asp:TableCell runat="server" ID="blogEntryIDCell" Visible="false"></asp:TableCell>
    <asp:TableCell runat="server" ID="blogEntryTitleCell" CssClass="blogEntryTitle">
    </asp:TableCell>
</asp:TableRow>
<asp:TableRow runat="server" CssClass="blogEntrySubTitleRow">
    <asp:TableCell runat="server" ID="blogEntrySubTitleCell">
    </asp:TableCell>
</asp:TableRow>
<asp:TableRow runat="server" CssClass="blogEntryDataRow">
    <asp:TableCell runat="server" ID="blogEntryDataCell">blogEntryData</asp:TableCell>
</asp:TableRow>
<asp:TableRow runat="server" CssClass="TagsRow">
    <asp:TableCell runat="server" ID="TagsCell" CssClass="TagsRow">
    <div class="TagsRow">Tags Row</div>
    </asp:TableCell>
</asp:TableRow>
<asp:TableRow runat="server" CssClass="blogEntryPermalinkRow">
    <asp:TableCell runat="server" ID="PermalinkCell"><div>PermaLink</div>
    </asp:TableCell>
</asp:TableRow>
<asp:TableFooterRow runat="server" CssClass="blogEntryFooterRow">
    <asp:TableCell runat="server" ID="blogEntryFooterCell">
    <div>footer row</div>
    </asp:TableCell>
</asp:TableFooterRow>
</asp:table>  

As you can see from the markup, the ID and CssClass names are rather long and VERY descriptive. I like to have that during development because it simplifies debugging infinitely. But when it comes to deployment, I actually use much shorter names (usually acronyms), this cuts down considerably on the amount of text being sent over the wire (or over the air for that matter). It�s a pain, but worth it when deploying something that would face heavy usage. As you're reading this article, always maintain the idea that this is not high-throughput production quality. The code was written in 5 days and the article across almost 3 weeks with little if any proper specs mapped out.

Anyways, if you're more experienced in HTML/XHTML development you can skip forward this following paragraph, if not then I hope it answers your question as to why DIV tags? You can also see my liberal usage of the <DIV></DIV> tags (in this control and everywhere else on my site, just check out the page source). The reason is because DIVs (from their names) are logical divisions on a page. You can apply formatting to a particular DIV tag and ignore the other DIVs. As well, since the DIV can be ID�ed, it is excellent for dynamic content, you know precisely where to place it. Finally, the DIV tag adds a line break before and after, thus emphasizing the section that it is. Exposing the attributes of the control as normal, updating each entry is a cinch:

    /// <summary>
    /// Exposes the BlogEntryID attribute of the underlying markup (blogEntryIDCell)
    /// </summary>
    public int BlogEntryID
    {
        get
        {
            return int.Parse(this.blogEntryIDCell.Text);
        }
        set
        {
            this.blogEntryIDCell.Text = value.ToString();
        }
    }

    /// <summary>
    /// Exposes the BlogEntryTitle attribute of the underlying markup
    /// (blogEntryTitleCell)
    /// </summary>
    public string BlogEntryTitle
    {
        get
        {
            return this.blogEntryTitleCell.Text;
        }
        set
        {
            //this will be a link on to the page with the single blog, complete.
            // Should be used as a permalink?
            this.blogEntryTitleCell.Text = CreateLink(value);
        }
    }

(And so on for the other attributes) Since we have (or will have I should say) many blog entries, we obviously need a data source to retrieve a set number (I chose 10) out of whatever the total number of blog entries that exist in the database. The rest can be paged as necessary. For the other elements existing, the blogEntry controls (Permalinks, Tags, and the Date) as can be seen are built into the actual control. Now, there�s a small trick involved with each of these. Each one of these links back to the blog page with a get request specifying which filter to use to get the proper entry or entries. So, I have four possible get requests for the blog page:

Form Description *Example
mustafacodes.net/blog.aspx Vanilla, entered manually or through the navigation bar and is the default way of accessing the blog page mustafacodes.net/blog.aspx
mustafacodes.net/blog.aspx?id={tag ID} ID, can be accessed through the Title or from the Permalink mustafacodes.net/blog.aspx?id=2
mustafacodes.net/blog.aspx?tag={tag text} Tagged, can be accessed through the tag cloud or through the tags found at the tag row of the blog entry mustafacodes.net/blog.aspx?tag=personal
mustafacodes.net/blog.aspx?date={month #, year #} Date, can be accessed from the archive list just below the tag cloud mustafacodes.net/blog.aspx?date=12,2007

*I have shortened the URL but that's to make it fit on the page.

At the moment I'm settling for the Permalink to be as is, simply a GET request with a blog entry ID. Hopefully, for the next article, I'll have URL re-writing implemented and therefore end up with something like http://www.mustafacodes.net/blog/aparticularblogentry.aspx.

So now all we need to worry about is the way the posts are handled at the blog.aspx page. I've got my Page_Load method as follows:

protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            if (!IsPostBack)
            {
                if (Request.QueryString.Count > 0)
                {
                    eBlogAction blogAction = CheckKey(Request.QueryString);

                    switch (blogAction)
                    {
                        case eBlogAction.GetByID:
                            GetBlogByID();
                            break;
                        case eBlogAction.GetByTag:
                            GetBlogByTag();
                            break;
                        case eBlogAction.GetByDate:
                            GetBlogByDate();
                            break;
                        case eBlogAction.DefaultAction:
                            //anything else we simply transfer back to
                            //the default blogs
                            Server.Transfer("blog.aspx");
                            break;
                        default:
                            //anything unhandled previously we simply transfer back to
                            // the blogs not the error pages (for now)
                            Server.Transfer("blog.aspx");
                            break;
                    }
                }
            }
        }
        catch (Exception ex)
        {
             //log the error
             using (ErrorLogger el = new DataLayer.ErrorLogger(ex,
              ConfigurationManager.ConnectionStrings["DbConnection"].ConnectionString));
             Response.Redirect("error.aspx", true);
        }
    }

Loading the Data

Now, I opted for the data handling and the display of the list of Blog Entries by a control dedicated to just that named blogEntries.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="blogEntries.ascx.cs"
                          Inherits="controls_blogEntries" %>
<%@ Register TagName="BlogEntry" Src="../controls/blogEntry.ascx" TagPrefix="uc" %>
<asp:Table runat="server" ID="blogEntries" Width="100%">
    <asp:TableRow runat="server">
        <asp:TableCell Width="100%" CssClass="blogEntriesBody" runat="server">
            <asp:Repeater runat="server" ID="rptBlog">
                <HeaderTemplate>
                    <table width="100%">
                </HeaderTemplate>
                <ItemTemplate>
                    <tr style="width: 100%;">
                        <td>
                            <div class="rptblogEntriesTemplate">
                                <uc:BlogEntry runat="server" ID="ItemBlogEntryItem" />
                            </div>
                        </td>
                    </tr>
                </ItemTemplate>
                <AlternatingItemTemplate>
                    <tr style="width: 100%;">
                        <td>
                            <div class="rptBlogAlternatingItemTemplate">
                                <uc:BlogEntry runat="server" ID="ItemBlogEntryItem" />
                            </div>
                        </td>
                    </tr>
                </AlternatingItemTemplate>
                <FooterTemplate>
                    </table>
                </FooterTemplate>
            </asp:Repeater>
        </asp:TableCell>
    </asp:TableRow>
</asp:Table>
<asp:Table runat="server" ID="btnTable" Width="100%">
    <asp:TableRow>
        <asp:TableCell HorizontalAlign="Left">
            <asp:LinkButton ID="rptPrev" runat="server" Text="<< Previous"
                     CommandName="rptPrev" Visible="false" />
        </asp:TableCell>
        <asp:TableCell HorizontalAlign="Right">
            <asp:LinkButton ID="rptNext" runat="server" Text="Next >>"
                     CommandName="rptNext" Visible="false" />
        </asp:TableCell>
    </asp:TableRow>
</asp:Table>

As you can see, it really comprises of a DataRepeater, which is infinitely more lightweight, that is bound on the server side to the PagingDataSource which is how the paging was implemented. I�m not going to post the complete code behind of the control here, suffice it to say that it's begging a re-write. Also note how the only difference that exists between the ItemTemplate and the AlternatingItemTemplate is the CSS class that is used. This is because the ONLY difference between either is a different background color that specifies the alternate rows. However, I will post the ItemDataBound Event handler (with all its simplicity):

private void rptBlog_ItemDataBound(object sender, RepeaterItemEventArgs e)
    {
        if(e.Item.ItemType == ListItemType.Item ||
           e.Item.ItemType == ListItemType.AlternatingItem)
        {

            //pass the values from (DataRowView)e.Item.DataItem[];
            controls_blogEntry cbe =
                          (controls_blogEntry)e.Item.FindControl("ItemBlogEntryItem");
            DsBlog.tblBlogEntryRow ber =
                          (DsBlog.tblBlogEntryRow)((DataRowView)e.Item.DataItem).Row ;

            if (ber != null)
            {
                cbe.BlogEntryID = ber.BlogEntryID;
                cbe.BlogEntryTitle = ber.BlogTitle;
                cbe.BlogEntrySubTitle = ber.BlogSubTitle;
                cbe.BlogEntryData = string.Format("<div>{0}</div><br />", ber.BlogEntry);
                string link = string.Format
                        ("http://www.mustafacodes.net/blog.aspx?id={0}",
                                            cbe.BlogEntryID);
                cbe.PermaLink = string.Format("<div class=\"blogEntryPermalinkRow\">" +
                                      "permalink: <a href=\"{0}\">{0}</a></div>", link);
                cbe.BlogEntryFooter = ber.BlogDate.ToLongDateString();

                string s = ber.Tags;
                if(s.Length > 0)
                {
                    cbe.Tags = s.Substring(1);
                }
                else
                {
                    cbe.Tags = s;
                }
            }
        }
    }

Blogging

For now, I don't have the luxury of having my server at my home. Also, as has been previously discussed, I like coding things for the sake of �I can�. So, and since this is my site, I wrote a WinForm application that simply accepts the text of my blog entry in HTML format (a big thank you to Patrick Sears and Code-Frog [Rex] for pointing out FCKEditor to me and its WinForms implementation) and sends it to a Web Service that accepts the data and inserts it into the database. The Web service and the Blog editor application are worthy of their own article, so I'll be posting them later on. But here�s a sneak preview.

BlogEntry03.png

Conclusion

This quick and nearly painless foray into the world of writing my own version of a Blogging engine took me about 5 days. The writing of the article took much longer. This, to me at least, proved that the concept behind the engine is simple and that the size of the other freely available open source engines seems to be completely unwarranted - as Marc has mentioned in his article here that other engines are big and clunky; well, Sub Text weighs in at a whopping 71.6 MB of source on my drive. The source code for my complete site including images and everything without the database is a measly 1.07 MB. Granted that ABE (for now) doesn't offer the same set of features but a difference of about 70 fold is still big in my opinion.

If you'd like to give ABE a run, please follow the steps in the installme.txt file you'll find in the source zip. If you feel that there is a feature that I need to include, then, by all means, send me an email. If you have any questions, I'll be more than happy to answer them.

I will continue work on ABE. I've already promised two more articles revolving around the usage of ABE and the more advanced features that are to be implemented. So keep on the lookout for new revisions and releases.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here