Introduction
Preamble
First of all, before I even touch anything remotely technical, I�d like to say two things:
- 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.
- 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:
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:
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 DIV
s (from their names) are logical divisions on a page. You can apply formatting to a particular DIV
tag and ignore the other DIV
s. 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:
public int BlogEntryID
{
get
{
return int.Parse(this.blogEntryIDCell.Text);
}
set
{
this.blogEntryIDCell.Text = value.ToString();
}
}
public string BlogEntryTitle
{
get
{
return this.blogEntryTitleCell.Text;
}
set
{
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:
Server.Transfer("blog.aspx");
break;
default:
Server.Transfer("blog.aspx");
break;
}
}
}
}
catch (Exception ex)
{
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)
{
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.
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.