Tom Phelps
Tom Phelps

Reputation: 11

ASP.NET Core MVC How to send ViewModel to different controller method

So I have 2 controller classes;

AnnouncementsController, this just generates a homepage of sorts with posts from users on it.

// GET: Announcements
    public async Task<IActionResult> Index()
    {
        var announcements = await _context.Announcement.ToListAsync();
        announcements = announcements.OrderByDescending(x => x.CreatedOn).ToList();
        foreach (var ann in announcements)
        {
            ann.TimeAgo = ann.CreatedOn.TimeAgo();
        }
        
        var users = await _context.Users.ToListAsync();
        var comments = await _context.Comment.ToListAsync();
        AnnouncementAndCommentsViewModel aacVM = new AnnouncementAndCommentsViewModel();

        AnnouncemenstViewModel announcemenstViewModel = new AnnouncemenstViewModel();
        announcemenstViewModel.Announcement = announcements;
        announcemenstViewModel.User = users;
        announcemenstViewModel.Comment = comments;

        CommentViewModel commentViewModel = new CommentViewModel();

        aacVM.announcemenstViewModel = announcemenstViewModel;
        aacVM.commentViewModel = commentViewModel;

        ViewData.Add("currentUserID",GetCurrentUser().Id);

        return View(aacVM);
    }

Then I have the CommentsController

[HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create(CommentViewModel commentViewModel)
    {
        if (ModelState.IsValid)
        {
            var comment = new Comment();
            comment.CreatedOn = DateTime.Now;
            comment.Body = commentViewModel.Comment.Body;

            Announcement announcement = GetAnnouncement(commentViewModel.Announcement.AnnouncementID);
            comment.Announcement = announcement;
            ApplicationUser user = GetCurrentUser();
            comment.User = user;

            _context.Add(comment);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
        return View(commentViewModel);
    }

From my AnnouncementController Index view, I have a list of posts and then I want to be able to write and add comments directly in that view as opposed to going to another view.

I have 2 ViewModels, one for storing announcements and one for comments.

I'm trying to call the Create Post method in the Comments Controller with its argument that accepts a CommentViewModel although I can't figure it out. I keep getting sent to the Create Get Method with the body filled in.

Here's my index view and how I'm trying to post the data to the CommentsController

@model Treharris.Models.AnnouncementAndCommentsViewModel;
.
.
.
@foreach (var item in Model.announcemenstViewModel.Announcement)
        {
.
.
.
<div class="announcement-body">
                    <p>@Html.DisplayFor(modelItem => item.Body)</p>
                </div>
                <div class="comments">
                    <p id="comment">Comments</p>
                    @using(Html.BeginForm("Create","Comments", FormMethod.Post, 
                     new { cvm = Model.commentViewModel }))
                    {
                        @@Model.commentViewModel.Announcement = item;
                        @Html.HiddenFor(m => m.commentViewModel.Announcement)
                        @Html.TextBoxFor(m => m.commentViewModel.Comment.Body, 
                        new { placeholder = "Comment body" })
                        <input type="submit" value="Create" />

                    }

Upvotes: 1

Views: 4572

Answers (1)

Jo&#227;o Pereira
Jo&#227;o Pereira

Reputation: 1667

As @Shyju commented, for model binding to work using the form extensions, the Model that you pass to the View has to be the same that the post action receives.

If you look at the HTML that gets generated,

@Html.HiddenFor(m => m.commentViewModel.Announcement)

outputs something as

<input type="hidden" name="commentViewModel.Announcement" value="propVal" />.

On the Action you expect a CommentViewModel object that has no property named commentViewModel, but has a property named Announcement. That is why the binding does not occur.

Either change the parameter on the post action method to match the View Model, but be aware that all properties that do not exist on the form are posted as null,

OR

drop the Html form extension methods that are being deprecated in .NET Core and use simple Html for these type of bindings as the follow:

<form asp-controller="Comments" asp-action="Create" method="post">
        <input type="hidden" name="Announcement" value="@Model.commentViewModel.Announcement" />
        <input type="text" name="Comment.Body" placeholder="Comment body" value="@Model.commentViewModel.Comment.Body" />
        <input type="submit" value="Create" />
</form>

Remember to include all the information regarding the Announcement object as hidden fields, as you cannot post complex objects on forms as you are trying to do. That is an HTML feature/limitation.

For example, simple include the announcement id in the form as:

<input type="hidden" name="Announcement.Id" value="@item.id" />

Upvotes: 2

Related Questions