MVC Create custom comment system in c# with javascript

3 min read

I recently created a custom blog engine for this blog and thought I'd share a simple overview of how I did it.

Demo: Simple validation demo to show custom comment validation

Creating the data models

In order to persist our comments in a data source, and retrieve them back, we need to define a data model that defines the information we require:

public class Comment
{
    [Required]
    public int Id { get; set; }

    [StringLength(50)]
    public string Author { get; set; }

    [Required]
    public string Body { get; set; }

    [StringLength(100)]
    public string Email { get; set; }

    [Required]
    public int PostId { get; set; }

    // Link to existing Post class 
    public virtual Post Post { get; set; }
}

I want to be able to access comments from a post so I also need to update my post model as follows:

public class Post
{
    // Current Properties...

    // New relationship property
    public virtual ICollection<Comment> Comments { get; set; }
}

Now I have my data models I simply need to create a data migration and update my database using the following commands

Add-Migration CreateComments -ProjectName Blog.Data -Verbose Update-Database -ProjectName Blog.Data -Verbose

Now that our database is up to date you may need to implement some code in order to access your data, be it a repository or otherwise.

Creating the view model

Now that the data can be persisted and retrieved from our datasource we can concentrate on how we allow users to create a comment. Firstly we need to create a viewmodel to represent a comment:

public class CommentViewModel
{
    //Represents a post id
    [Required]
    public int Id { get; set; }
    [Required]
    [StringLength(50)]
    public string Author { get; set; }
    [Required]
    [AllowHtml]
    public string Body { get; set; }
    [Required]
    [StringLength(100)]
    [DataType(DataType.EmailAddress)]
    [EmailAddress]
    public string Email { get; set; }
}

Creating the HttpPost action

We also need to create a post action in our controller to accept the comment form submissions. This will be a simple MVC post action like any other:

[HttpPost]
public ActionResult Comment(CommentViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        //Mapping code - alternatively try AutoMapper
        var dataComment = new Comment();
        dataComment.PostId = viewModel.Id;
        dataComment.Author = viewModel.Author;
        dataComment.Body = viewModel.Body;
        dataComment.Email = viewModel.Email;
            
        // Create comment and save changes
        commentRepository.Create(comment);
        commentRepository.SaveChanges();

        return new EmptyResult();
    }

    var modelErrors = this.BuildModelErrors();
    // return a bad request to signify that adding the comment failed
    HttpContext.Response.StatusCode = 400;
    // return errors as Json, read by javascript
    return Json(modelErrors);
}

/// <summary>
/// Build a list of model errors from model state.  
/// This method flattens the model state errors.
/// </summary>
/// <returns>A list of Keys and Error messages</returns>
private List<ModelError> BuildModelErrors()
{
    var modelErrors = new List<ModelError>();
    var erroneousFields = this.ModelState.Where(ms => ms.Value.Errors.Any())
                                         .Select(x => new {x.Key, x.Value.Errors});

    foreach (var erroneousField in erroneousFields)
    {
        var fieldKey = erroneousField.Key;
        var fieldErrors = erroneousField.Errors.Select(error => 
                                            new ModelError(fieldKey, error.ErrorMessage));
        modelErrors.AddRange(fieldErrors);
    }
    return modelErrors;
}
    
//Class to hold model errors and the corresponding field key
private class ModelError
{
    public ModelError(string key, string errorMessage)
    {
        Key = key;
        ErrorMessage = errorMessage;
    }

    public string Key { get; set; }
    public string ErrorMessage { get; set; }
}        

If the view model fails validation I return a HttpStatusCodeResult with a HttpStatusCode.BadRequest (400) status code. This will ensure that our response is not treated as a success in our javascript, but more on that further down

Creating the form

We now need to create a form that can be used to create a comment. I have created this as a partial view so that I can add it easily to multiple pages

I could have simply created a simple Html.BeginForm() or Ajax.BeginForm() but I wanted a custom implementation with cleaner html so I decided to use plain html and javascript with a lot of help from jquery.

@model Blog.Models.Comments.CommentViewModel

<div class="comment-form-container">
    <form class="comment-form" data-action="@Url.Action("Comment", "Comments")">
        @Html.HiddenFor(m => m.Id)
        <div class="comment-body">
            <div>@Html.LabelFor(m => m.Author)</div>
            @Html.TextBoxFor(m => m.Author, new { Class = "comment-name" })
        </div>
        <div>
            <div>@Html.LabelFor(m => m.Email)</div>
            @Html.TextBoxFor(m => m.Email, new { Class = "comment-email" })
        </div>
        <div>
            <div>@Html.LabelFor(m => m.Body)</div>
            @Html.TextAreaFor(m => m.Body, new { Class="comment-body", rows="3", cols="50" })
        </div>
        <div class="comment-result" style="display: none;" >
            <span class="comment-result-text">An error occurred</span>
        </div>
        <div>
            <button type="submit" class="comment-form-submit">Submit comment</button>
        </div>
    </form>
</div>

Now we have a form we simply need to hook up the form submission

The javascript

Below is some javascript that can be used to post our form to the controller in order to submit a form:

$('.comment-form-container').on('click', '.comment-form-submit', function(e) {
	// prevent form submission
	e.preventDefault();
	var form = $(this).closest('form');
	var resultMessage = $('.comment-result', form);
	resultMessage.hide();

	var submitButton = $(this);
	// disable the submit button to stop 
	// accidental double click submission
	submitButton.attr('disabled', 'disabled');
	var resultMessageText = $('.comment-result-text', form);

	// client side validation
	if (validateComment(form, resultMessageText) == false) {
		resultMessage.addClass('comment-result-failure');
		resultMessage.fadeIn();
		// Re-enable the submit button
		submitButton.removeAttr('disabled');
		return;
	}

	var postUrl = form.data('action');
	var postData = form.serialize();

	$.post(postUrl, postData)
		.done(function(data) {
			resultMessage.addClass('comment-result-success');
			resultMessageText.html('Comment created successfully!');
			// Clear submitted value
			$('.comment-body', form).val('');
		})
		.fail(function() {
			resultMessage.addClass('comment-result-failure');
			resultMessageText.html('An error has occurred');
		})
		.always(function() {
			resultMessage.fadeIn();
			submitButton.removeAttr('disabled');
		});
});

I have also added some simple validation to check, client side, that required fields have values. This is in place to simply prevent unnecessarily posting back to the server if we already know a required field hasn't been given a value.

// validate required fields
var validateRequired = function (input) {
    if (input.val().length == 0) {
        input.addClass('input-validation-error');
        return false;
    }
    return true;
};

var validateComment = function (commentForm, resultMessageContainer) {
    var isValid = true;
    var errorText = '';
    var name = $('.comment-name', commentForm);
    if (!validateRequired(name)) {
        errorText += 'The Name field is required.<br />';
        isValid = false;
    }

    var body = $('.comment-body', commentForm);
    if (!validateRequired(body)) {
        errorText += 'The Body field is required.<br />';
        isValid = false;
    }

    var email = $('.comment-email', commentForm);
    if (!validateRequired(email)) {
        errorText += 'The Email field is required.<br />';
        isValid = false;
    }

    if (isValid == false) {
        resultMessageContainer.html(errorText);
    }

    return isValid;
};	

In the following post I will show how we can leverage jquery validate to perform the client side validation for us automatically to replace our custom validation.

Don't forget to check out the demo

Demo: Simple validation demo to show custom comment validation

Any comments, please let me know using the form below.


Comments
Dean says:November 29, 2013

Your comment count is missing a :hover color :-p

Nice blog mate :-)

mukesh says:May 9, 2014

hello john nye, where r u displaying comments after being submitted...?

John says:May 13, 2014

Hi Mukesh,

The Demo page doesn't display the comments as it's purpose is more to demonstrate the validation. If validation is passed and the server receives the comment, how you handle and display the comment is up to you.

The same system is used on this blog and I use the data posted to create a new comment entry instead of receiving new data.

Hope this helps

Brian says:June 29, 2014

Ideally or in today's WWW you want to be able to edit comments and delete comments using AJAX. That is where things can get tricky IMO. Having comments in textbox containers in a forloop was my first attempt, but it is just not clean enough for me. What do you suggest for html and how could I pass the comment's ids for AJAX? Thanks in advance

Brian says:June 29, 2014

Oh, I forgot if anyone is interested in attempting to create commenting section using knockout.js this is a great tutorial but once again uses texbox/textarea http://techbrij.com/facebook-wall-posts-comments-knockout-aspnet-webapi

John says:July 1, 2014

Hi Brian,

Editing becomes a bit more tricky but I do currently have the delete functionality on my blog site. Basically, I have created a new action result in controller called Delete

[HttpPost]
public ActionResult Delete(int id)
{
    this.commentRepository.Delete(id);
    this.commentRepository.SaveChanges();
    return new EmptyResult();
}

This coupled with the following javascript

$(function () {
    // Setup on click delete action across all comments
    $(&#39;.blog-comments&#39;).on(&#39;click&#39;, &#39;.delete-comment&#39;, function () {
        // find the related post html
        var post = $(this).closest(&#39;.blog-comment&#39;);
        // Get the post id from the post data attribute
        var postData = { id: post.data(&#39;post-id&#39;) };

        // Make a request to delete the post
        $.post(&#39;/posts/delete&#39;, postData)
            .always(function () {
                // Remove the post html from page gracefully
                post.slideUp(function () {
                    post.remove();
                });
            });
    });
})

Hope this helps.

Furkan says:June 8, 2016

Nice!

robin says:August 23, 2016

nice good

Meee says:September 10, 2016

Niceee

sedat says:January 18, 2017

very good

nvm says:April 24, 2017

Nice

Tony says:July 17, 2017

Amazing

brad says:August 1, 2017

very good

saira says:August 15, 2017

wow

SirLucky says:August 29, 2017

Its actually something like this i want for my site

za says:October 21, 2017

Hi

XYz says:October 31, 2017

Nice

Bob says:November 3, 2017

<b>thanks</b> for <b>sharing</b>

akber says:May 13, 2019

pretty good work

Parmarvipul says:March 7, 2020

Nice

Vikas says:July 21, 2020

Hii

ada says:March 15, 2021

asd

eee says:July 2, 2021

oh

NULL says:August 30, 2021

NULL

NULL says:August 30, 2021

NULL

te says:September 11, 2021

te

Mahesh says:September 16, 2022

KKkcjsjsndjdddx

mo says:September 26, 2022

okokok