I am working on an ASP.NET Core MVC project that involves posts and comments. Users can leave comments on posts, and they can also reply to other comments. The initial implementation was working (Check this commit), but after some refactoring, the reply functionality stopped working. Now, when I try to reply to a comment, the page just refreshes without hitting the controller action.
Previous Working Implementation:
Controller Action for Adding Comment:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddComment(Comment comment)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
comment.AuthorId = user.Id;
comment.CreatedAt = DateTime.Now;
_context.Comments.Add(comment);
await _context.SaveChangesAsync();
}
return RedirectToAction(nameof(Details), new { id = comment.PostId });
}
Post Index View:
@using System.Security.Claims
@model IEnumerable<ArianNovinWeb.Models.Post>
@{
ViewData["Title"] = "Posts";
}
<div class="container">
<h1 class="mb-4">Posts</h1>
<p>
<a asp-action="Create" class="btn btn-primary">Create New Post</a>
</p>
<div class="row">
@foreach (var post in Model)
{
<div class="col-md-6 mb-4">
<div class="card">
@if (!string.IsNullOrEmpty(post.ImagePath))
{
<img src="@post.ImagePath" class="card-img-top" alt="Post Image" />
}
<div class="card-body">
<h5 class="card-title">@post.Title</h5>
<p class="card-text">@post.Description</p>
<p class="card-text"><small class="text-muted">Created by @post.Author?.UserName on @post.CreateDate.ToString("g")</small></p>
<h6>Comments</h6>
@if (post.Comments != null && post.Comments.Any())
{
@foreach (var comment in post.Comments.Where(c => c.ParentCommentId == null))
{
@await Html.PartialAsync("_CommentPartial", comment)
}
}
else
{
<p>No comments yet. Be the first to comment!</p>
}
<!-- Comment form -->
<form asp-action="AddComment" method="post" class="mt-3">
<input type="hidden" name="PostId" value="@post.PostId" />
<div class="mb-3">
<label for="Content" class="form-label">Comment:</label>
<textarea id="Content" name="Content" rows="3" class="form-control" required></textarea>
</div>
<button type="submit" class="btn btn-secondary">Add Comment</button>
</form>
<div class="mt-3 d-flex justify-content-between">
@if (User.Identity.IsAuthenticated && post.AuthorId == User.FindFirstValue(System.Security.Claims.ClaimTypes.NameIdentifier))
{
<div>
<a asp-action="Edit" asp-route-id="@post.PostId" class="btn btn-outline-primary btn-sm">Edit</a>
<a asp-action="Delete" asp-route-id="@post.PostId" class="btn btn-outline-danger btn-sm">Delete</a>
</div>
}
<a asp-action="Details" asp-route-id="@post.PostId" class="btn btn-outline-info btn-sm">Details</a>
</div>
</div>
</div>
</div>
}
</div>
</div>
Comment Partial View:
@model ArianNovinWeb.Models.Comment
<div class="card mb-2">
<div class="card-body">
<p class="card-text">@Model.Content</p>
<p class="card-text">
<small class="text-muted">
Posted by @Model.Author?.UserName on @Model.CreatedAt.ToString("g")
</small>
</p>
<!-- Reply form -->
<form asp-action="AddComment" method="post" class="mt-2">
<input type="hidden" name="PostId" value="@Model.PostId" />
<input type="hidden" name="ParentCommentId" value="@Model.CommentId" />
<div class="mb-2">
<textarea name="Content" class="form-control" rows="2" placeholder="Reply..." required></textarea>
</div>
<button type="submit" class="btn btn-secondary btn-sm">Reply</button>
</form>
<!-- Render replies recursively -->
@if (Model.Replies != null && Model.Replies.Any())
{
<div class="ml-4 mt-2">
@foreach (var reply in Model.Replies)
{
@await Html.PartialAsync("_CommentPartial", reply)
}
</div>
}
</div>
</div>
Current Non-Working Implementation:
Controller Action for Adding Comment:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddComment(int postId, string content, int? parentCommentId)
{
if (!ModelState.IsValid)
{
TempData["ErrorMessage"] = "Failed to add comment. Please try again.";
return RedirectToAction("Index", new { id = postId });
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return Forbid();
}
var comment = new Comment
{
PostId = postId,
Content = content,
AuthorId = user.Id,
CreatedAt = DateTime.Now,
ParentCommentId = parentCommentId
};
_context.Comments.Add(comment);
await _context.SaveChangesAsync();
TempData["SuccessMessage"] = "Comment added successfully!";
return RedirectToAction("Index", new { id = postId });
}
Post Index View:
@using System.Security.Claims
@model ArianNovinWeb.ViewModels.PostIndexViewModel
@{
ViewData["Title"] = "Post Details";
}
<div class="container mt-4">
@if (Model.ShowShareButton)
{
<div class="text-center mt-5">
<h2>No Posts Available</h2>
<p>Be the first to share a post!</p>
<a asp-action="Create" class="btn btn-primary">Share Post</a>
</div>
}
else
{
<div class="row">
<div class="col-md-2 mt-10">
@await Html.PartialAsync("_LatestItemsPartial", Model.LatestPosts)
</div>
<div class="col-md-6">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<a asp-action="Create" class="btn btn-primary">Share Post</a>
</div>
<div>
@if (Model.PostNavigation.PreviousPostId.HasValue)
{
<a asp-action="Index" asp-route-id="@Model.PostNavigation.PreviousPostId" class="btn btn-primary">Previous</a>
}
@if (Model.PostNavigation.NextPostId.HasValue)
{
<a asp-action="Index" asp-route-id="@Model.PostNavigation.NextPostId" class="btn btn-primary">Next</a>
}
</div>
</div>
<div class="card">
<h3 class="card-header">@Model.PostNavigation.Post.Title</h3>
<div class="card-body">
<h5 class="card-title">By: @Model.PostNavigation.Post.Author</h5>
<h6 class="card-subtitle text-muted">Posted at: @Model.PostNavigation.Post.CreateDate</h6>
</div>
@if (!string.IsNullOrEmpty(Model.PostNavigation.Post.ImagePath))
{
<img src="@Model.PostNavigation.Post.ImagePath" alt="Post Image" class="img-fluid" />
}
<div class="card-body">
<p class="card-text">@Model.PostNavigation.Post.Description</p>
</div>
<div class="mt-3 d-flex justify-content-between">
@if (User.Identity.IsAuthenticated && @Model.PostNavigation.Post.AuthorId == User.FindFirstValue(System.Security.Claims.ClaimTypes.NameIdentifier))
{
<div>
<a asp-action="Edit" asp-route-id="@Model.PostNavigation.Post.PostId" class="btn btn-outline-primary btn-sm">Edit</a>
<a asp-action="Delete" asp-route-id="@Model.PostNavigation.Post.PostId" class="btn btn-outline-danger btn-sm">Delete</a>
</div>
}
<a asp-action="Details" asp-route-id="@Model.PostNavigation.Post.PostId" class="btn btn-outline-info btn-sm">Details</a>
</div>
</div>
<!-- Comment form -->
<div class="card mt-3 ">
<div class="card-body ">
<h3>Leave a Comment</h3>
<form asp-action="AddComment" method="post">
<input type="hidden" name="PostId" value="@Model.PostNavigation.Post.PostId" />
<div class="form-group">
<label for="Content">Comment:</label>
<textarea id="Content" name="Content" rows="4" required class="form-control"></textarea>
</div>
<button type="submit" class="btn btn-primary mt-2">Add Comment</button>
</form>
</div>
</div>
</div>
<div class="col-md-4 mt-3">
<div class="comment-section">
<h3>Comments</h3>
<!-- Display comments -->
@if (Model.PostNavigation.Post.Comments.Any())
{
@foreach (var comment in Model.PostNavigation.Post.Comments)
{
<div class="comment mb-3">
@await Html.PartialAsync("_CommentPartial", comment)
</div>
}
}
else
{
<p>No comments yet.</p>
}
</div>
</div>
</div>
}
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
<script>
$(document).ready(function () {
var successMessage = '@TempData["SuccessMessage"]';
if (successMessage) {
toastr.success(successMessage);
}
var errorMessage = '@TempData["ErrorMessage"]';
if (errorMessage) {
toastr.error(errorMessage);
}
});
</script>
}
Comment Partial View:
@model ArianNovinWeb.Models.Comment
<div class="card border-primary mb-3">
<div class="card-body">
<p class="card-header">
@if (Model.Author.UserName != null)
{
<small class="text-black">
Post by @Model.Author.UserName at @Model.CreatedAt
</small>
}
else
{
<small class="text-black">
Post by @Model.Author.Email at @Model.CreatedAt
</small>
}
</p>
<p class="card-text">@Model.Content</p>
<!-- Reply form -->
<form asp-action="AddReply" method="post">
@Html.AntiForgeryToken()
<input type="hidden" name="postId" value="@Model.PostId" />
<input type="hidden" name="parentCommentId" value="@Model.CommentId" />
<div class="mb-2">
<textarea name="content" class="form-control" rows="2" placeholder="Reply..." required></textarea>
</div>
<button type="submit" class="btn btn-secondary btn-sm">Reply</button>
</form>
<!-- Render replies recursively -->
@if (Model.Replies != null && Model.Replies.Any())
{
<div class="ml-4 mt-2">
@foreach (var reply in Model.Replies)
{
@await Html.PartialAsync("_CommentPartial", reply)
}
</div>
}
</div>
</div>
PostIndexViewModel:
using ArianNovinWeb.Models;
using ArianNovinWeb.ViewModels;
namespace ArianNovinWeb.ViewModels
{
public class PostIndexViewModel
{
public List<Post> Posts { get; set; }
public PostNavigationViewModel PostNavigation { get; set; }
public LatestItemsVM LatestPosts { get; set; }
public bool ShowShareButton { get; set; }
}
}
Issue:
When I submit a reply, it refreshes the post index view and nothing happens. The action method AddComment
is never hit, and I can’t figure out why. There are no errors in the browser console, and the form seems to be posting correctly.