asp.netsqlc#-4.0threaded-comments

asp.net threaded comments?


I am working on creating a blog in ASP.net 4.0 and sql server 2008 and would like to learn how to create a threaded comments system. By threaded I mean each comment would have a reply link and the comments are indented under the comment that it is a reply to. So, you can either respond to the article itself or reply to any of the comments.

This is very common on forums and blogs but I cannot find any articles that would explain and show code example on how this is done.

The following is what I have created but it only works one level of depth. I'd like to make it recursive so there is no limit to the level of depth:. How can I do this? Any advice, articles with code samples would be awesome!

Comments db Table

commentId
parentId
postId
date
author
authorEmail
authorURL
authorIP
content
IsApproved

ASP.NET Markup:

<asp:ListView ID="ListView1" runat="server" onitemdatabound="ListView1_ItemDataBound">
<ItemTemplate>
    <div class="commentwrap">
        <div class="commentsTitleArea">
            <span class="commentCounter"><%# Convert.ToInt32(Container.DisplayIndex) + 1%>. </span>&nbsp;&nbsp;<img src="../images/decoy-icon-16px.png" alt="Comment by..." title="Comment by..." class="blogCommentIcon" /><a href='<%# Eval("AuthorUrl")%>' target="_blank" rel="nofollow"><%# " " + Eval("Author")%></a>&nbsp;&nbsp;<%# Eval("Date")%></div>
        <div class="commentText">
            <%# Eval("Content") %>
            <div><span class="btnCommentReply"><a href='<%# "article.aspx?article=" + Request.QueryString["article"] + "&amp;cid=" + Eval("commentId") + "#comment" %>'>REPLY</a></span></div>
        </div>
        <asp:ListView ID="ListView2" runat="server">
            <ItemTemplate>
                <div class="commentwrap commentNest">
                    <div class="commentsTitleArea">
                        <span class="commentCounter"><%# Convert.ToInt32(Container.DisplayIndex) + 1%>. </span>&nbsp;&nbsp;<img src="../images/decoy-icon-16px.png" alt="Comment by..." title="Comment by..." class="blogCommentIcon" /><a href='<%# Eval("AuthorUrl")%>' target="_blank" rel="nofollow"><%# " " + Eval("Author")%></a>&nbsp;&nbsp;<%# Eval("Date")%></div>
                    <div class="commentText">
                        <%# Eval("Content") %>
                    </div>
                </div>
            </ItemTemplate>
            <EmptyDataTemplate>
            </EmptyDataTemplate>
            <LayoutTemplate>
                <div id="itemPlaceholderContainer" runat="server">
                    <span id="itemPlaceholder" runat="server" />
                </div>
            </LayoutTemplate>
        </asp:ListView>
    </div>
</ItemTemplate>
<EmptyDataTemplate>
</EmptyDataTemplate>
<LayoutTemplate>
    <div id="itemPlaceholderContainer" runat="server">
        <span id="itemPlaceholder" runat="server" />
    </div>
    <div class="dataPagerWrap">
        <asp:DataPager ID="ListViewpager" runat="server" PagedControlID="ListView1" PageSize="30" QueryStringField="page">
            <Fields>
                <asp:NextPreviousPagerField ShowFirstPageButton="True" ShowNextPageButton="False" ShowPreviousPageButton="False" FirstPageText="«" ButtonCssClass="dataPagerBackForward" />
                <asp:NumericPagerField ButtonCount="8" CurrentPageLabelCssClass="dataPagerCurrent" NumericButtonCssClass="dataPager" PreviousPageText="..." NextPageText="..." NextPreviousButtonCssClass="dataPagerBackForward" />
                <asp:NextPreviousPagerField ShowLastPageButton="True" ShowNextPageButton="False" ShowPreviousPageButton="False" LastPageText="»" ButtonCssClass="dataPagerBackForward" />
            </Fields>
        </asp:DataPager>
    </div>
    <div class="padding"></div>
</LayoutTemplate>


    ALTER PROCEDURE [dbo].[sp_blog_GetComments]
(
    @article int
)
AS
    SET NOCOUNT ON;

    SELECT 
    post_Comments.Author, 
    post_Comments.AuthorEmail, 
    post_Comments.AuthorUrl, 
    post_Comments.Content, 
    post_Comments.Date, 
    post_Comments.commentId 
    FROM post_Comments 
    INNER JOIN 
    posts 
    ON post_Comments.postId = posts.postId 
    WHERE(post_Comments.postId = @article) 
    AND (post_Comments.IsApproved = 1) 
    AND (post_Comments.ParentId IS NULL) 
    AND (posts.IsPublished = 1) 
    AND (posts.PublishOnDate <= GETDATE())

    SELECT 
    Author, 
    AuthorEmail, 
    AuthorUrl, 
    Content, 
    Date, 
    ParentId
    FROM post_Comments
    WHERE (postId = @article) 
    AND (IsApproved = 1)

Stored Procedure:

ALTER PROCEDURE [dbo].[Sp_blog_getcomments] (@article INT)
AS
    SET nocount ON;

    SELECT post_comments.author,
           post_comments.authoremail,
           post_comments.authorurl,
           post_comments.content,
           post_comments.date,
           post_comments.commentid
    FROM   post_comments
           INNER JOIN posts
                   ON post_comments.postid = posts.postid
    WHERE ( post_comments.postid = @article )
          AND ( post_comments.isapproved = 1 )
          AND ( post_comments.parentid IS NULL )
          AND ( posts.ispublished = 1 )
          AND ( posts.publishondate <= Getdate() )

    SELECT author,
           authoremail,
           authorurl,
           content,
           date,
           parentid
    FROM   post_comments
    WHERE  ( postid = @article )
           AND ( isapproved = 1 ) 

Solution

  • One approach I remember seeing somewhere was rather than using a Comment ID and a Parent ID , comments had a Comment ID and a "sort key" which was a concatenation of all the Comment IDs of their ancestors.

    E.g. If comment 1 had two replies, comments 2 and 3, the sort keys would be:

    1 : 0001
    2 : 0001.0002
    3 : 0001.0003
    

    Then if someone replied to comment 2, it would be..

    4 : 0001.0002.0004
    

    So if you select all comments and sort by this sort key, they'll fall out in the right order.

    Then, to do the indenting, you simply look at the length of the sort key to see how many levels deep the comment is, and indent an appropriate amount.

    Adding comments is easy: the sort key of the new comment is simply it's parent's sort key, with its own ID added on to the end.