Reputation: 316
I found out that my page actually reload/refresh, but require additional reload to see the content I just added. However I've to reload again to see the data, OR add another data to see the previous data..
I added my controller code below:
(CreateComment and Comment(display comments) are inside Details(details about book) View )
CreateComment:
public ActionResult CreateComment(Guid id) {
return View(new CommentToBook { BookId = id });
}
[HttpPost]
public ActionResult CreateComment(CommentToBookVm model) {
if (ModelState.IsValid) {
var m = new CommentToBook { Comment = model.Comment, BookId = model.BookId };
m.UserId = new Guid(Session["UserID"].ToString());
m.CreatedDate = DateTime.Now;
db.CommentToBooks.Add(m);
db.SaveChanges();
}
return View(model);
}
View for createComment
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.HiddenFor(s => s.BookId)
<div class="form-horizontal">
<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>
Details (have Comment and CreateComment inside)
public ActionResult Details(Guid? id) {
Book book = db.Books.Find(id);
return View(book);
}
View
<h2>Details</h2>
@Html.Action("Rating", new { id = Model.Id })
<div>
<h4>Books</h4>
<hr/>
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd>
@Html.DisplayFor(model => model.Title)
</dd>
@*and so on...*@
</dl>
</div>
@Html.Action("Comment", new { id = Model.Id })
@Html.Action("CreateComment", new { id = Model.Id })
And the Comment to list all comments.
public ActionResult Comment(Guid? id) {
var comment = db.CommentToBooks.Where(c => c.BookId == id);
return View(comment.ToList());
}
view:
<table class="table">
<tr>
<th>
User
</th>
<th>
@Html.DisplayNameFor(model => model.Comment)
</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.ActionLink(item.User.UserName, "VisitUser", new { id = item.UserId })
</td>
<td>
@Html.DisplayFor(modelItem => item.Comment)
</td>
</tr>
}
</table>
I think it's about what I am returning in controller, I tried some different alternative but I just end up hitting the error:
Description:
An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Source Error:
Line 9: @Html.Action("Comment", new { id = Model.Id })
OR
child actions are not allowed to perform redirect actions.
If I try to RedirectToAction
for CreateComment
etc.
I would appreciate an example of code as I find it hard to understand new concepts by just words.
Upvotes: 1
Views: 9673
Reputation: 218892
Your code is returning the view for CreateComment
method. Looks like you marked this action method as ChildActions
only. You should not be using ChildAction for a use case like this. ChildActions should be used for rendering something to the view. Ex : A Menu bar in your app.
Even if you remove the [ChildAction]
from the CreateComment action method, when you return the model back to the form, it is going to render the markup generated by the CreateComment view. That means you will loose the list of comments (which was loaded when you called the Details view).
Ideally, for all data insertion usecases, you should follow P-R-G pattern.
PRG stands for POST - REDIRECT- GET. That means, you submit a form and after successfully saving data to db, you do return a redirect result to the client and client(browser) will issue a totally new http request for the GET action method in which you wiil query the db table and return the results.
But since your form is loaded to the main view through a call to the Html.Action
method, you won't get the desired results (list of comments and the validation messages in same view). One thing to make it to work is, by enabling unobtrusive client side validation. In that case, the form won't be actually submitted to the server. Instead client side validation will be invoked and validation messages will be shown to the user in the same page(no page reload!).
You can enable it by adding reference to these 2 scripts in your view(or layout)
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
Redirect to the GET action to show all comments after successful save of the posted comment.
[HttpPost]
public ActionResult CreateComment(CommentToBookVm model)
{
if (ModelState.IsValid)
{
//your existing code to save data to the table
return RedirectToAction("Details","Book", new { id=model.BookId} );
}
// Hoping that the below code might not execute as client side validation
// must have prevented the form submission if there was a validation error.
return View(model);
}
Another option which does not rely on just client side validation is to create a flat view model which has the list of existing comments and properties for the new comment form. When you submit the form, if validation fails, reload the Comments property again and return the view model back to the form.
public class ListAndCreateVm
{
[Required]
public string NewComment { set;get;}
public Guid BookId { set;get;}
public List<CommentVm> Comments { set;get;}
}
public class CommentVm
{
public string Comment { set;get;}
public string Author { set;get;}
}
and in your Details action, load Comments and send this to the view
public ActionResult Details(Guid id)
{
var vm = new ListAndCreateVm { BookId= id};
vm.Comments = GetComments(id);
return View(vm);
}
private List<CommentVm> GetComments(Guid bookId)
{
return db.CommentToBooks.Where(c => c.BookId == bookId)
.Select(x=> new CommentVm { Comment = x.Comment})
.ToList();
}
and in your view
@model ListAndCreateVm
@foreach(var c in Model.Comments)
{
<p>@c.Comment</p>
}
<h4>Create new comment</h4>
@using(Html.BeginForm())
{
@Html.ValidationSummary(false, "", new {@class = "text-danger"})
@Html.TextBoxFor(s=>s.NewComment)
@Html.HiddenFor(f=>f.BookId)
<input type="submit" />
}
Now along with using PRG pattern, make sure to reload Comments property of the view model when Model validation fails
[HttpPost]
public ActionResult Details(ListAndCreateVm model)
{
if(ModelState.IsValid)
{
// to do : Save
return RedirectToAction("Details,"Book",new { id=model.BookId});
}
//lets reload comments because Http is stateless :)
model.Comments = GetComments(model.BookId);
return View(model);
}
Upvotes: 5