MVC Create custom comment system in c# with javascript
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.
Your comment count is missing a :hover color :-p
Nice blog mate :-)
hello john nye, where r u displaying comments after being submitted...?
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
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
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
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
$('.blog-comments').on('click', '.delete-comment', function () {
// find the related post html
var post = $(this).closest('.blog-comment');
// Get the post id from the post data attribute
var postData = { id: post.data('post-id') };
// Make a request to delete the post
$.post('/posts/delete', postData)
.always(function () {
// Remove the post html from page gracefully
post.slideUp(function () {
post.remove();
});
});
});
})
Hope this helps.
Nice!
nice good
Niceee
very good
Nice
Amazing
very good
wow
Its actually something like this i want for my site
Hi
Nice
<b>thanks</b> for <b>sharing</b>
pretty good work
Nice
Hii
asd
oh
NULL
NULL
te
KKkcjsjsndjdddx
okokok