How to build a comments system in asp.net core or PHP

How to build a comments system in asp.net core or PHP

Introduction 

A comments system is a back boon for any blog, article, and social media website so that users can share their opinions.

So, in this article, we will implement comment systems by using ASP.net core MVC, C#.net, Jquery, and Bootstrap. Moreover, we can use this comment system in PHP or other web development technologies because I have used Jquery for front end development.

I have used VS2019 and SQL Server for development.

Please follow the following steps:-

Note: This article is assuming that you have already developed the functionality of saving Users, Articles or blogs and now you are looking for comments system for your website.

If you are a front-end developer then, you can jump to jquery code 

I am going used VS2019 and SQL Server and please follow the following steps:-

Create a Database (I have used SQL database, so you can use MYSQL also)

1: Article or Blog Tabel

2: Comments Table 

3: Stored Procedures for Comments binding

Create Proc [dbo].[GetArticleComments]--GetArticleComments  11  
(  
@ArticleId bigint  
)  
as  
select articleComm.Id,articleComm.ParentId,articleComm.CommentText,convert(varchar, articleComm.CommentedDate, 22)as CommentedDate, articleComm.CommentedById  
,userProfile.FirstName as UserName
 from UserArticleComments as articleComm  
inner join   
UsersProfile as userProfile on userProfile.UserId=articleComm.CommentedById  
and articleComm.ParentId=0 and articleComm.ArticleId=@ArticleId  
union all  
select articleComm.Id,articleComm.ParentId,articleComm.CommentText,convert(varchar, articleComm.CommentedDate, 22)as CommentedDate, articleComm.CommentedById  
,userProfile.FirstName  as UserName
 from UserArticleComments as articleComm  
inner join   
UsersProfile as userProfile on userProfile.UserId=articleComm.CommentedById  
and articleComm.ParentId<>0 and articleComm.ArticleId=@ArticleId  
order by ParentId asc  
  

For backend development, I have used C#.net and entity framework core code first.

1: Create service class and Interface, like following the screenshot  

and create functions for saving and fetch comments data from the database 

 public class ArticleCommentsList
    {
        public Int64 Id { get; set; }
        public Int64 ParentId { get; set; }
        public string CommentText { get; set; }
        public string CommentedDate { get; set; }
        public Int64 CommentedById { get; set; }
        public string UserName { get; set; }
    }


     public async Task AddComment(ArticleCommentViewMoldel model)
        {
            try
            {
                        UserArticleCommentsDataModel userArticleCommentsData = new UserArticleCommentsDataModel
                        {
                            CommentText = model.CommentText,
                            CommentedBy = await _unitOfWork.UserLoginRepository.GetByID(model.UserId),
                            Article=await _unitOfWork.UserCommunityArticlesRepository.GetByID(model.ArticleId),
                            ParentId=model.ParentId,
                            IsActive=true
                        };
                        await _unitOfWork.ArticleCommentsRepository.Insert(userArticleCommentsData);

                        var _userProfile = await _unitOfWork.UserProfileRepository.FindAllBy(c => c.User.Id == model.UserId);
                        ArticleCommentsList articleComments = new ArticleCommentsList {
                            Id= userArticleCommentsData.Id
                            ,CommentedDate=userArticleCommentsData.CommentedDate.ToString()
                            ,CommentText=model.CommentText
                            ,CommentedById=model.UserId
                            ,ParentId=userArticleCommentsData.ParentId
                            ,UserName= _userProfile[0].FirstName
                        };
                     
                    return articleComments;
               
            }
            catch (System.Exception)
            {

                throw;
            }
        }

You can use own code for calling SQL stored procedure 

  public async Task> GetArticleComments(long ArticleId)
        {
            IList list = new List();
            await _context.LoadStoredProc("GetArticleComments")
                       .WithSqlParam("ArticleId", ArticleId)
                       .ExecuteStoredProcAsync((handler) =>
                       {
                           list = handler.ReadToList();
                           // do something with your results.
                       });
            
            return list;
        }


2: Create ArticleCommentsController controller in asp.net core project for calling service functions and paste following code

[HttpPost]
        public async Task addNewComment(ArticleCommentViewMoldel model)
        {
            try
            {
                model.UserId = _claimAccessor.UserId;
                var result =await _articleCommentsService.AddComment(model);
                return Json(new { status = true, messsage="Thanks", result = result });

            }
            catch (Exception)
            {
                //Handle Error here..
            }

            return Json(new { error = true });
        }
       
        public IActionResult AddArticleCommentPartial()
        {
            ViewBag.Id = _claimAccessor.UserId;
            return PartialView("_addComment");
        }
        public async Task LoadArticleCommentsAsync(long articleId)
        {
            return Json(await _articleCommentsService.GetArticleComments(articleId));
        }

jquery code
3: Add a "_addComment.cshtml" partial view under "ArticleComments" folder and paste following code

<div class="form-group mb-4" id="ArticleComment">
    <div class="row">
        <div class="col-md-12">
            <textarea class="form-control" style="height:80px"  placeholder="Start the discussion..." id="txtArticleComment" message="comment text is a required field." required ></textarea>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12 text-right article-main-comment">
            @if(ViewBag.Id!=0){
            <button type="submit" onclick="AddArticleComment();" id="btnPost" disabled class="btn btn-primary">COMMENT</button>
            }
            else{
                <b>What are your thoughts? Log in  or Sign up</b>
            }
        </div>
    </div>
</div>
<script>
 $(document).ready(function () {
    $('#txtArticleComment').keyup(function () {
        
        if (this.value.length > 0) {
           $("#btnPost").removeAttr("disabled");
        }
        else {
             $("#btnPost").attr("disabled", true);
        }
    });
});
</script>

4: Add Comment.js file and paste the following code and also, please add toastr js from online


function AddArticleComment() {
    if (formValidation('ArticleComment') === false) {
        return false;
    }
    var dataModel = {
        ArticleId: $('#ArticleId').val(),
        ParentId: 0,
        CommentText: $('#txtArticleComment').val()
    };

    postArticleComment(dataModel);
    $('#txtArticleComment').val('');
  
  
}
function postArticleCommentReply(parentId)
{
   
     var dataModel = {
         ArticleId: $('#ArticleId').val(),
            ParentId: parentId,
            CommentText: $('#txtArticleReply'+parentId).val()
        };
 postArticleComment(dataModel);
}
function postArticleComment(dataModel)
{
    if (dataModel.CommentText.trim().length === 0) {
        toastr.error("comment box is empty, please share you thoughts", 'Warning', { timeOut: 5000 });
        return;
    }
  $.ajax({
        type: 'POST',
        data: { model: dataModel },
        url: '/ArticleComments/addNewComment',
        success: function (data) {
            console.log(JSON.stringify(data));
            if (data.status) {
               
                toastr.success(data.messsage, '', { timeOut: 1000 });
                appendArticleComment(data.result);
            }
            else {
                
                toastr.success(data.messsage, 'Warning', { timeOut: 5000 });
            }
        },
        failure: function (response) {
            // alert(response.responseText);
            swal("Sorry!", response.responseText, "error");
        },
        error: function (response) {
            swal("Sorry!", response.responseText, "error");
        }

    });
}

var list = "";
function loadArticleCommentsAjax(articleId) {
    list = '';
    $('#divComments').html('loding comments..');
    $.ajax({
        type: 'POST',
        data: { articleId: articleId },
        url: '/ArticleComments/LoadArticleCommentsAsync',
        success: function (data) {
            //console.log(JSON.stringify(data));
            getNestedChildren(data, 0);
            //console.log( getNestedChildren(data, 0));
            //console.log(list);
            $("#divComments").html(list);
        },
        failure: function (response) {
            // alert(response.responseText);
            swal("Sorry!", response.responseText, "error");
        },
        error: function (response) {
            swal("Sorry!", response.responseText, "error");
        }

    });
}

function getNestedChildren(arr, parent) {
    //var _button = '<div><i class="fa fa-comment-alt" ></i > <input type="button" class="replycomment" onclick="addAticleCommentBox(this,@item.Id);" value="Reply"></div>';
    list += "<ul>";
    for (var i in arr) {

        if (arr[i].parentId === parent) {
            list += '<li><div class="comment-profile">'+arr[i].userName+'&nbsp;&nbsp;'+arr[i].commentedDate+'</div>' + arr[i].commentText;
            list += '<div><i class="fa fa-comment-alt fa-sm" ></i > <input type="button" class="replycomment" onclick="addAticleCommentBox(this,' + arr[i].id + ');" value="Reply"></div>';
            //bind child comments
            getNestedChildren(arr, arr[i].id);

            list += '</li>';
            list += '<div id="' + arr[i].id + '"><ul></ul></div>';
        }
    }
    list += "</ul>";
}

function appendArticleComment(result) {

    //var _li = ' <li>' + result.commentText + '</li>';
var _li='<li><div class="comment-profile">'+result.userName+'&nbsp;&nbsp;'+result.commentedDate+'</div>' +result.commentText;;
    var _replyDiv = '<div><i class="fa fa-comment-alt fa-sm" ></i > <input type="button" class="replycomment" onclick="addAticleCommentBox(this,' + result.id + ');" value="Reply"></div>';
    var _nextDiv = '<div id="' + result.id + '"><ul></ul ></div >';
    if (result.parentId === 0) {
        $('#divComments').children('ul').append( _li + _replyDiv + _nextDiv);
    } else {
        $('#' + result.parentId).children('ul').append(_li + _replyDiv + _nextDiv);
        removeArtcleCommentBox(result.parentId);
    }
    
    
}


function addAticleCommentBox(_this, _Id) {
    //if comment box is already opened then following if will close that box
    if ($('#divArticleCommentBox' + _Id).length)        
    {
        removeArtcleCommentBox(_Id);
        return;
    }
    //else add comment box near by reply button
    var _strHtml = '<textarea class="form-control" style="height:80px" placeholder="Start the discussion..." id="txtArticleReply' + _Id + '" message="comment text is a required field." required="" spellcheck="true"  onkeyup="articleWordCount(this,' + _Id + ')"></textarea>';
    var _strButtonOne = '<div class="comment-box-btn"><button type="submit" onclick="removeArtcleCommentBox(' + _Id + ');" id="btnArticleCancel' + _Id + '"  class="btn btn-link">CANCEL</button>';
    var _strButtonTwo = '&nbsp;&nbsp;<button type="submit" onclick="postArticleCommentReply(' + _Id + ');" id="btnArticleReply' + _Id + '" disabled="" class="btn btn-primary">REPLY</button></div>';
    $(_this).closest('div').append('<div class="comment-box" id="divArticleCommentBox' + _Id + '">' + _strHtml + _strButtonOne + _strButtonTwo + '</div>');
}
function removeArtcleCommentBox(_Id) {
    $('#divArticleCommentBox' + _Id).remove();
}
function articleWordCount(_this,_id) {
    if (_this.value.length > 0) {
        //alert(_this.value.length);
        $("#btnArticleReply" + _id).removeAttr("disabled");
    }
    else {
        $("#btnArticleReply" + _id).attr("disabled", true);
    }
}


4: Last step, add comment.js file on the article detail page and paste following code

<input type="hidden" id="ArticleId" value="@Model.Id" />
@*<partial name="/Views/ArticleComments/_addComment.cshtml" />*@
<div id="divAddCommentBox">

</div>
<div id="divComments">

</div>
<script>
    $('#divAddCommentBox').load('/ArticleComments/AddArticleCommentPartial');
   //$('#divComments').load('/ArticleComments/LoadArticleComments?articleId='+@Model.Id +'&parentId=0');
    loadArticleCommentsAjax(@Model.Id);
</script>

5: use the following CSS

/*COMMENTS */
#divComments ul {
    position: relative;
}
#divComments ul:before {
    content: "";
    background: #ddd;
    width: 2px;
    height: 100%;
    position: absolute;
    left: 0;
}
#divComments > ul:first-child:before {
    display:none;
}
#divComments > ul:first-child{
    padding:0;
}
#divComments ul li {
    display: inline-block;
}
#divComments ul .replycomment {
    background: none;
    border: none;
}
#divComments ul .replycomment {
    background: none;
    border: none;
    font-size: 12px;
    color: #ea9c0c;
    font-weight: bold;
}
#divComments ul i.fa-comment-alt {
    color: #ea9c0c;
}
#divComments ul li {
    display: inline-block;
    color: #666;
    width:100%;
}
#divComments .comment-box button.btn,
.article-main-comment button.btn{
    padding: 2px 5px;
    font-size: 11px;
}
#divComments .comment-box {
    margin-top: 5px;
    margin-bottom:10px;
}
#divComments .comment-box textarea {
    margin-bottom: 5px;
}
.comment-box-btn {
    text-align: right;
}
#divComments ul:hover:before {
    background: #ea9c0c;
}
#divComments ul .replycomment:focus {
    border: none;
    box-shadow: none;
    outline: none;
}
.comment-profile {
    font-size: smaller;
    color: cornflowerblue;
}


now you run the project and you will get the following output


I hope you like this article, please post your comments.

Thanks!


Thanks, for reading the blog, I hope it helps you. Please share this link on your social media accounts so that others can read our valuable content. Share your queries with our expert team and get Free Expert Advice for Your Business today.


About Writer

Ravinder Singh

Full Stack Developer
I have 12+ years of experience in commercial software development. I write this blog as a kind of knowledge base for myself. When I read about something interesting or learn anything I will write about it. I think when writing about a topic you concentrate more and therefore have better study results. The second reason why I write this blog is, that I love teaching and I hope that people find their way on here and can benefit from my content.

Hire me on Linkedin

My portfolio

Ravinder Singh Full Stack Developer