ASP.NET MVC3: Displaying Validation Errors from Child View in Parent View
I'm trying to add a form to allow users to comment on posts on my blogging application. So far, I've added a form to the post details view and I can submit comments, adding them to my database correctly. However, I have a problem with displaying validation errors to the user. The comment form is contained within a partial view and is rendered using Html.RenderAction
inside the post details view. I'd like to stress that I don't want to use AJAX for this as I'd like to approach this from a progressive enhancement point-of-view.
Here's the relevant posting action:
[HttpPost, Authorize]
public ActionResult AddComment(CommentViewModel newComment)
{
if (ModelState.IsValid)
{
Comment comment = new Comment(_userRepository.GetByUsername(User.Identity.Name));
Mapper.Map(newComment, comment);
_commentRepository.Add(comment);
_postsRepository.CommentAdded(comment.Article);
return RedirectToAction("Index", new { id = newComment.PostID });
}
// What do I do here?
}
I've tried several ways of returning views here but my issue is further complicated by some controller parameter validation that I have going on in the parent action:
//
// GET: /Posts/5/this-is-a-slug
public ActionResult Index(int id, string slug)
{
PostViewModel viewModel = new PostViewModel();
var model = _postsRepository.GetByID(id);
if (model != null)
{
if (slug == null || slug.CompareTo(model.Slug) != 0)
{
return RedirectToActionPermanent("Index", new { id, slug = model.Slug });
}
else
{
_postsRepository.PostVisited(model);
Mapper.Map(model, viewModel);
viewModel.AuthorName = _userRepository.GetById(model.AuthorID);
}
}
return View(viewModel);
}
This action basically mimics how SO's URLs work. If a post ID is supplied, the post is fetched from the database along with a slug which is created when the post is created. If the slug in the URL doesn't match the one in the database, it redirects to include the slug. This is working nicely but it does mean I'm having issues trying to populate my parent viewmodel, which is the following:
public class PostViewModel
{
public int PostID { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public string Slug { get; set; }
public DateTime DatePublished { get; set; }
public int NumberOfComments { get; set; }
public int AuthorID { get; set; }
public string AuthorName { get; set; }
public List<CommentViewModel> Comments { get; set; }
public CommentViewModel NewComment { get; set; }
}
What I was hoping woul开发者_如何学运维d work is to populate PostViewModel.NewComment
, test to see if it has data and then using it to display any model errors. Unfortunately, I'm lost as to how to accomplish that. This question helped me shape my approach, but it didn't quite answer my problem.
Could someone give me a gentle push in the right direction? If my approach seems unreasonable, I'd love to find out why and what a potential fix would be.
Many thanks in advance.
Forgot to fill in my answer here. For anyone that happens to stumble on this, the answer was to use TempData
to store the ModelState
errors and then repopulating ModelState
in the relevant controller action.
Firstly, I declared a key in the controller which would be used to reference the data inside TempData
. I decided to base this on the CommentViewModel
type as both actions depend on it.
public class PostsController : Controller
{
private static readonly string commentFormModelStateKey = typeof(CommentViewModel).FullName;
// Rest of class.
}
In this first action, the code checks to see if TempData
contains data assigned to the key. If it does, it's copied into ModelState
.
// GET: /posts/comment
[ChildActionOnly]
public PartialViewResult Comment(PostViewModel viewModel)
{
viewModel.NewComment = new CommentViewModel(viewModel.PostID, viewModel.Slug);
if (TempData.ContainsKey(commentFormModelStateKey))
{
ModelStateDictionary commentModelState = TempData[commentFormModelStateKey] as ModelStateDictionary;
foreach (KeyValuePair<string, ModelState> valuePair in commentModelState)
ModelState.Add(valuePair.Key, valuePair.Value);
}
return PartialView(viewModel.NewComment);
}
This action determines if the ModelState
is valid before adding a comment to the database. If the ModelState
is not valid, it is copied to TempData
, which makes it available to the first action.
// POST: /posts/comment
[HttpPost, Authorize]
public ActionResult Comment(CommentViewModel newComment)
{
if (!ModelState.IsValid)
{
TempData.Add(commentFormModelStateKey, ModelState);
return Redirect(Url.ShowPost(newComment.PostID, newComment.Slug));
}
// Code to add a comment goes here.
}
精彩评论