2

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 ) 
Sathyajith Bhat
  • 21,321
  • 22
  • 95
  • 134
  • And what's the problem/question/issue that you have? – Tony Abrams Sep 21 '10 at 18:31
  • @Tony Abrams, I made the question bold on top. –  Sep 21 '10 at 18:39
  • Can anyone tell me how to extend this one more level? I realize that nobody knows how to create threaded comments but perhaps this question might be doable. –  Sep 21 '10 at 21:07

1 Answers1

10

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.

Carson63000
  • 4,215
  • 2
  • 24
  • 38
  • Thanks for the reply! What data type would I use for this field? –  Sep 22 '10 at 20:30
  • 1
    Just a varchar.. make it long enough to support whatever limit you want to place on number of comments and nesting depth. e.g. if you limit to 9999 comments per post, and replies 5 levels deep, the longest sort key would be like "9999.9999.9999.9999.9999" so you'd need varchar(24) – Carson63000 Sep 23 '10 at 00:37
  • 1
    Carson63000, Thank you for the suggestion. It was a little bit tricky to figure out all the details but I managed to get it done. The hardest part was to get the number of dots to assign the css for the indent. The stored procedures were rather simple. For comments the sorting value is set equal to its own comment id value and for comment replies the value is set equal to the parent sorting id concatenated with it's own comment id separated by a dot. If a sorting id has no dots, it is a top level comment. One dot equals one level depth, two equals two level depth etc. Thank you! –  Sep 23 '10 at 06:55
  • Does anyone have an idea on how to query the data if you would like the comments in descending order instead. That will say that the newest comments is printed first. – Abris Jun 27 '13 at 18:39
  • what would the schema look like for this? – chovy Nov 20 '21 at 08:11