This is a three part series, if you haven’t already read part one, you may want to start there (An Example of Writing Your Own Blog Engine (mini-version)), or part two (An Example of Writing Your Own Blog Engine (mini-version) Part Two).
Presentation Layer
Ok, well this will be the last piece to the puzzle and we’re going to start with the main thread which will house the featured blog post itself as well as a mechanism for handling comments. We want to make it in such a way that we can easily control the number of blog posts that are displayed. For example, if we wanted to display one featured post (the most recent in this example), or allow a user to select a month and display all blog posts for that month. It seems to me that a repeater sounds like a nice, flexible, .NET control to handle this situation.
Here is the following HTML I used, you can of course change this to however fits
your needs.
HTML and ASP.NET Controls
<asp:Repeater ID="rptMainList" runat="server"> <ItemTemplate> <!-- Blog date --> <div><asp:Literal ID="litPublishDate" runat="server" Text='<%#Eval("PubDate").ToLongDateString()%>'></asp:Literal></div> <hr /> <h1><asp:Literal ID="litBlogTitle" runat="server" Text='<%#Eval("Title")%>'></asp:Literal></h1> <asp:Literal ID="litBlogPost" runat="server" Text='<%#Eval("Description")%>'></asp:Literal> <asp:HiddenField ID="hdnBlogID" runat="server" Value='<%#Eval("BlogID")%>' /> <hr /> <!-- Blog: Add a comment --> <table cellpadding="2" cellspacing="4" width="100%"> <tr> <td style="width: 35%;">Enter your name:</td> <td style="width: 65%;">Have a comment? Post it here.</td> </tr> <tr> <td><input type="text" id="tbName" runat="server" style="width: 100%;" /></td> <td rowspan="5" style="vertical-align: top;"><textarea id="tbComment" runat="server" cols="1" rows="1"></textarea></td> </tr> <tr> <td>Enter your email:</td> </tr> <tr> <td><input type="text" id="tbEmail" runat="server" style="width: 100%;" /></td> </tr> <tr> <td>Enter your URL here:</td> </tr> <tr> <td><input type="text" id="tbURL" runat="server" style="width: 100%;" /></td> </tr> <tr> <td colspan="2" style="text-align: right;"> <asp:LinkButton ID="btnPostComment" runat="server" Text="add comment" CommandName="PostComment"></asp:LinkButton> </td> </tr> </table> <hr /> <!-- Blog: Previous comments --> <asp:UpdatePanel ID="upnlBlogComments" runat="server"> <Triggers> <asp:AsyncPostBackTrigger ControlID="btnPostComment" EventName="Click" /> <asp:AsyncPostBackTrigger ControlID="rptMainList" EventName="ItemCommand" /> </Triggers> <ContentTemplate> <asp:Repeater ID="rptBlogComments" runat="server" OnItemDataBound="rptBlogComments_ItemDatabound"> <HeaderTemplate> <table cellpadding="2" cellspacing="4" width="100%" class="comments"> <tr> <td colspan="2"><h3 style="padding-bottom: 0px; margin-bottom: 0px;"><span style="color: Black;"><asp:Label ID="lblNumberOfComments" runat="server"></asp:Label></span> Comments</h3></td> </tr> </HeaderTemplate> <ItemTemplate> <tr> <td> <asp:Label ID="lblDate" runat="server"></asp:Label><br /> <asp:Label ID="lblPoster" runat="server"></asp:Label><br /> <asp:HyperLink ID="lnkURL" runat="server"></asp:HyperLink> </td> <td style="padding-left: 25px;"> <asp:Label ID="lblComment" runat="server" Text=""></asp:Label> </td> </tr> </ItemTemplate> <AlternatingItemTemplate> <tr> <td colspan="2"><hr /></td> </tr> <tr> <td> <asp:Label ID="lblDate" runat="server"></asp:Label><br /> <asp:Label ID="lblPoster" runat="server"></asp:Label><br /> <asp:HyperLink ID="lnkURL" runat="server"></asp:HyperLink> </td> <td style="padding-left: 25px;"> <asp:Label ID="lblComment" runat="server" Text=""></asp:Label> </td> </tr> <tr> <td colspan="2"><hr /></td> </tr> </AlternatingItemTemplate> <FooterTemplate> </table> </FooterTemplate> </asp:Repeater> </ContentTemplate> </asp:UpdatePanel> </ItemTemplate> </asp:Repeater>
VB Code for Handling the Main Post
Now, we also need a bit of code to handle working with the blog post itself, we’ll reference what we created in the business layer to do this. Notice the use of a helper function to handle repetive code, this is good practice to reduce redundant code making maintenance easier. Also, I prefer to standardize the way I title these types of functions, it will also help in ease of maintenance.
Private _blogbll As BlogBLL Public Property BlogAdapter() As BlogBLL Get If _blogbll Is Nothing Then _blogbll = New BlogBLL End If Return _blogbll End Get Set(ByVal value As BlogBLL) _blogbll = value End Set End Property ''' <summary> ''' Get's most recent blog post. ''' </summary> ''' <remarks></remarks> Private Sub GetBlogPost() Dim blogPostDT As Blog.BlogPostsViewDataTable = BlogAdapter.GetMostRecentBlogPost() Try GetBlogPost_Helper(blogPostDT) Catch ex As Exception 'If this fails, it means there are no blog posts in database at all 'Should maybe redirect to a plain / non-blog page Exit Sub End Try End Sub ''' <summary> ''' Get's specific blog post. ''' </summary> ''' <param name="BlogID"></param> ''' <remarks></remarks> Private Sub GetBlogPost(ByVal blogID As Integer) Dim blogPostDT As Blog.BlogPostsViewDataTable = BlogAdapter.GetBlogPost(blogID) GetBlogPost_Helper(blogPostDT) End Sub ''' <summary> ''' Get's all blog posts for a given month/year (archived posts) ''' </summary> ''' <param name="month"></param> ''' <param name="year"></param> ''' <remarks></remarks> Private Sub GetBlogPost(ByVal month As Integer, ByVal year As Integer) Dim blogPostDT As Blog.BlogPostsViewDataTable = BlogAdapter.GetBlogPosts(month, year) GetBlogPost_Helper(blogPostDT) End Sub Private Sub GetBlogPost_Helper(ByVal blogPostDR As Blog.BlogPostsViewDataTable) rptMainList.DataSource = blogPostDR rptMainList.DataBind() End Sub Protected Sub rptBlogPosts_ItemDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.RepeaterItemEventArgs) Handles rptBlogPosts.ItemDataBound Select Case e.Item.ItemType Case ListItemType.Item rptBlogPosts_Helper(sender, e) Case ListItemType.AlternatingItem rptBlogPosts_Helper(sender, e) End Select End Sub Private Sub rptBlogPosts_Helper(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.RepeaterItemEventArgs) Dim lnkBlogPost As HyperLink = DirectCast(e.Item.FindControl("lnkBlogPost"), HyperLink) Dim title As String = e.Item.DataItem("Title").ToString().Replace(" ", "_").Replace(".", "").Replace(",", "") lnkBlogPost.Text = e.Item.DataItem("Title").ToString() 'lnkBlogPost.NavigateUrl = String.Format("default.aspx?blogid={0}", e.Item.DataItem("BlogID").ToString()) lnkBlogPost.NavigateUrl = String.Format("{0}-{1}", e.Item.DataItem("BlogID").ToString(), title) End Sub
VB Code for Handling Blog Comments
The same needs to be done with the blog comments.
Private Sub rptBlogComments_Helper(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.RepeaterItemEventArgs) Dim lblDate As Label = DirectCast(e.Item.FindControl("lblDate"), Label) Dim lblPoster As Label = DirectCast(e.Item.FindControl("lblPoster"), Label) Dim lnkURL As HyperLink = DirectCast(e.Item.FindControl("lnkURL"), HyperLink) Dim lblComment As Label = DirectCast(e.Item.FindControl("lblComment"), Label) Dim dateCreated As DateTime Dim pubDate As String = "Unknown" Try dateCreated = e.Item.DataItem("PubDate") pubDate = dateCreated.ToLongDateString() Catch ex As Exception End Try lblDate.Text = pubDate Dim poster As String = e.Item.DataItem("Username").ToString() If String.IsNullOrEmpty(poster) = True Then poster = "anonymous" End If lblPoster.Text = poster lblComment.Text = e.Item.DataItem("Comment").ToString() Dim url As String = e.Item.DataItem("URL").ToString() lnkURL.Text = url lnkURL.NavigateUrl = url End Sub Protected Sub rptBlogComments_ItemDatabound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.RepeaterItemEventArgs) Select Case e.Item.ItemType Case ListItemType.Header Dim blogID As Integer = hdnTempBlogID.Value Dim lblNumberOfComments As Label = DirectCast(e.Item.FindControl("lblNumberOfComments"), Label) lblNumberOfComments.Text = BlogAdapter.GetNumberOfBlogComments(blogID).ToString() Case ListItemType.Item rptBlogComments_Helper(sender, e) Case ListItemType.AlternatingItem rptBlogComments_Helper(sender, e) End Select End Sub Protected Sub rptMainList_ItemCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.RepeaterCommandEventArgs) Handles rptMainList.ItemCommand Select Case e.CommandName Case "PostComment" Dim hdnBlogID As HiddenField = DirectCast(e.Item.FindControl("hdnBlogID"), HiddenField) Dim tbComment As HtmlTextArea = DirectCast(e.Item.FindControl("tbComment"), HtmlTextArea) Dim tbName As HtmlInputText = DirectCast(e.Item.FindControl("tbName"), HtmlInputText) Dim tbEmail As HtmlInputText = DirectCast(e.Item.FindControl("tbEmail"), HtmlInputText) Dim tbURL As HtmlInputText = DirectCast(e.Item.FindControl("tbURL"), HtmlInputText) Dim blogID As Integer = hdnBlogID.Value Dim comment As String = tbComment.Value Dim username As String = tbName.Value Dim email As String = tbEmail.Value Dim url As String = tbURL.Value Try BlogAdapter.CreateBlogComment(blogID, comment, username, email, url) Catch ex As Exception 'FIX add error handling here End Try 'Retrieve blog comments Dim rptBlogComments As Repeater = DirectCast(e.Item.FindControl("rptBlogComments"), Repeater) rptBlogComments.DataSource = BlogAdapter.GetBlogComments(blogID) rptBlogComments.DataBind() End Select End Sub Protected Sub rptMainList_ItemDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.RepeaterItemEventArgs) Handles rptMainList.ItemDataBound hdnTempBlogID.Value = e.Item.DataItem("BlogID") 'Retrieve blog comments Dim rptBlogComments As Repeater = DirectCast(e.Item.FindControl("rptBlogComments"), Repeater) rptBlogComments.DataSource = BlogAdapter.GetBlogComments(e.Item.DataItem("BlogID")) rptBlogComments.DataBind() End Sub
Finally, the Trigger
All the trigger does (in this case on page load) is handle the possible incoming data variations as well as kick off the whole process. As you can see below we have a few variations of data that can be passed in (and more in the real blog and yours as you add to it).
First, no additional data can be passed in and the blog engine will default to the most recent post only. Alternatively, you could pass in the month and year and retrieve a list of blog posts for that month. And finally, you can pass in a blog id and retrieve a specific blog post to read.
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load If Page.IsPostBack = False Then Dim month As Integer = 0 Try month = Integer.Parse(Request.QueryString("month")) Catch ex As Exception End Try Dim year As Integer = 0 Try year = Integer.Parse(Request.QueryString("year")) Catch ex As Exception End Try Dim blogID As Integer = 0 Try blogID = Integer.Parse(Request.QueryString("blogid")) Catch ex As Exception End Try If month > 0 And year > 0 Then GetBlogPost(month, year) ElseIf blogID > 0 Then 'Populate requested blog post GetBlogPost(blogID) Else 'Populate most recent blog post GetBlogPost() End If End If End Sub
And that’s it! In just three short installments we put together a basis for your very own blog engine which I encourage all of you to build upon, rewrite, improve, and generally play around with. Please leave comments on new ideas for the blog engine, updates to how you’ve enhanced it, or any bugs found within this base (there’s always a few).
What a truly fun writing..