1

I have a MVC5 project where we're creating an IMDB style clone. It's for games, movies and books instead of just movies.

The page has Review Feed, where you are shown a list of all Reviews, you can also add new reviews.

When I click on a review, I get to that reviews details page (~Views/Reviews/Details.cshtml), and so far so good. It shows all the details, no problems.

In the Reviews/Details view, I want to add a comment section. So I added this to my Review/Details.cshtml page:

<h4>Comments</h4>

@{
    Html.RenderPartial("_CommentPartial");
}

@foreach (var comment in Model.CommentToReviews)
{
    <div class="row">
        <div class="col-xs-2">
            @Html.Raw(comment.User.Username)
            <br /> 
            posted at: @Html.Raw(comment.CreatedDate.ToShortDateString())
        </div>
        <div class="col-xs-10">
            @Html.Raw(comment.Comment)
        </div>
    </div>
}

The problem is that I'm not sure how to pass on the current review-ID to the create-comment partial view.

This is the partial:

@model gmbdb.CommentToReview

@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Add a comment</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.Comment, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Comment, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Comment, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

And this is the Create get/post methods in CommentToReviewsController,

Get:

public ActionResult Create(Guid reviewId)
{
      var newComment = new CommentToReview();
      newComment.UserId = ((User) Session["currentUser"]).Id;
      newComment.ReviewId = reviewId;
      return View(newComment);
}

Post:

[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "Id,UserId,ReviewId,Comment,CreatedDate")] CommentToReview commentToReview)
        {
            if (ModelState.IsValid)
            {
                commentToReview.Id = Guid.NewGuid();
                commentToReview.CreatedDate = DateTime.Now;

                //find current Review and add this comment to that review
                foreach (var review in db.Reviews)
                {
                    if (review.Id == commentToReview.ReviewId)
                    {
                        review.CommentToReviews.Add(commentToReview);

                        db.SaveChanges();
                    }
                }

                return RedirectToAction("Index");
            }

            ViewBag.ReviewId = new SelectList(db.Reviews, "Id", "Title", commentToReview.ReviewId);
            ViewBag.UserId = new SelectList(db.Users, "Id", "Username", commentToReview.UserId);
            return View(commentToReview);
        }
Kim Skogsmo
  • 441
  • 3
  • 13

1 Answers1

1

Change your Details view to call an action instead of a partial, so you be able to receive the Id of the current review:

Views/Reviews/Details.cshtml

<h4>Comments</h4>
@{
    Html.RenderAction("Create", new { reviewId = Model.Id });
}

Create the action as follows:

public ActionResult Create(Guid reviewId)
{
    var newComment = new CommentToReview
    {
        UserId = UserId,
        ReviewId = reviewId
    };

    return PartialView("_CommentPartial", newComment); //return the partial from the action
}

Now your create action receives the id of the review and you can associate it to the model and return the _CommentPartial partial view.

The only thing to take care of now is than in your Post action you can not do any redirects because it's now a child action, so you could return a JsonResult with to the view and call via ajax and add the comment without refreshing your page.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,UserId,ReviewId,Comment,CreatedDate")] CommentToReview commentToReview)
{
    if (ModelState.IsValid)
    {
        //the code to add the comment

        return Json(new
        {
            success = true,
            //other data for the comment, maybe the rendered comment as html to display it on the view.
        });
    }

    ViewBag.ReviewId = new SelectList(db.Reviews, "Id", "Title", commentToReview.ReviewId);
    ViewBag.UserId = new SelectList(db.Users, "Id", "Username", commentToReview.UserId);
    return View(commentToReview);
}

Hope this helps!

Karel Tamayo
  • 3,690
  • 2
  • 24
  • 30
  • Thanks! @Html.RenderAction is what I was looking for. I changed it to ("Create", "CommentToReviews", etc...) to point to the right controller. In that controller I already have actions for adding it to the right reviews comments. (the ones you can see in my OP) However, now I get a nasty error! " New transaction is not allowed because there are other threads running in the session. " It highlights the line "db.SaveChanges()" after that message. Any idea? – Kim Skogsmo Oct 26 '16 at 22:07
  • Take a look at [this thread](http://stackoverflow.com/questions/2113498/sqlexception-from-entity-framework-new-transaction-is-not-allowed-because-ther): It seems that this issue has several possible causes, but I don't think it has something to do with the way your are calling your action now – Karel Tamayo Oct 26 '16 at 22:11
  • 1
    Gotcha! I figured it out. Short answer for anyone else who might be reading this and having the same problem: Don't db.SaveChanges() inside a foreach loop! Do it after! Thanks again :) – Kim Skogsmo Oct 26 '16 at 23:37