Search code examples
asp.net-mvc-4razorasp.net-mvc-viewmodelhtml.beginform

How can I pass the ViewModel back to the same page


I am trying to get a HTTPPost working for a comment section of a video page but am having some difficulty on postback. The save is being recorded to the database but on reload of the page I am not able to find the @Model.SelectedMediaItem.Name variable that was found the first time. My html is as follows:

@{
ViewBag.Title = "Screencast";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@model Project.ViewModels.MediaViewModel
<body onload="initialize()">

<div class="container region3wrap_screencast">
  <div class="row content_top_contact">
    <div class="nine columns">
      <ul class="breadcrumbs">
        <li><a href="@Url.Action("Index","Home")">Home</a></li>
        <li><a href="@Url.Action("Index","Media")">Media</a></li>
        <li><a href="@Url.Action("Screencast","Media")">Screencast</a></li>
        <li class="current"><a href="#">@Model.SelectedMediaItem.Name</a></li>
      </ul>
    </div>
  </div>
</div>

<div class="twelve columns leave-comment">
  <h3>Leave a Comment</h3>
  @using(Html.BeginForm("Screencast","Media", FormMethod.Post, Model))
  {              
    <div class="row">
      <div class="six columns">
        <div class="row">
          <div class="six columns">
            @Html.LabelFor(c => c.FeedbackComment.UserID)
            @Html.TextBoxFor(c => c.FeedbackComment.UserID)
          </div>
          <div class="six columns">
            @Html.LabelFor(c => c.FeedbackComment.ContentID)
            @Html.TextBoxFor(c => c.FeedbackComment.ContentID)
          </div>   
          <div class="row">
            <div class="twelve columns">
              @Html.LabelFor(c => c.FeedbackComment.FeedbackString)
              @Html.TextAreaFor(c => c.FeedbackComment.FeedbackString)
            </div>
          </div>        
        </div>
      </div>
    </div>          
    <input type="submit" value="Submit button" class="medium button bottom20"/>
  }
</div>
</body>

My controller actions are as follows:

  //GET
  public ActionResult Screencast(int ID)
  {                        
    mvm = new ViewModels.MediaViewModel(ID);
    return View(mvm);
  }

  //POST
  [HttpPost]
  public ActionResult Screencast(MediaViewModel mvm)
  {
    Feedback newFeedback= new Feedback();
    newFeedback.UserID = mvm.FeedbackComment.UserID;
    newFeedback.ContentID = mvm.FeedbackComment.ContentID;
    newFeedback.CourseID = null;
    newFeedback.Timestamp = DateTime.Now;
    newFeedback.FeedbackString = mvm.FeedbackComment.FeedbackString;

    //Initialize the Feedback Repository and save changes
    feedbackRepository = new Repository<Feedback>(dbcontext);
    feedbackRepository.Add(newFeedback);
    feedbackRepository.SaveChanges();

    return View(mvm);
  }

When I call the page to begin with at the URL /Media/Screencast/1 it loads with a viewmodel populated with details of the SelectedMediaItem and all appears as is expected.

When I try to post back to this my viewmodel detail is being lost although the comment is actually saved as expected I need to visit another page and revisit before I am able to see.

How can I pass my view model in the @Html.BeginFor as an additional parameter?

My ViewModel is as follows:

public class MediaViewModel
{
    private Repository<Content> contentRepository;
    private Repository<Feedback> feedbackRepository;
    private MetaLearningContext dbcontext;

    public int screencastID { get; set; }

    public IEnumerable<Content> Screencasts { get; set; }
    public IEnumerable<Content> Podcasts { get; set; }
    public IEnumerable<Content> Documents { get; set; }
    public IEnumerable<Feedback> FeedbackComments { get; set; }
    public Content SelectedMediaItem { get; set; }
    public MetaLearningUser User { get; set; }
    public MetaLearningUser FeedbackAuthor { get; set; }
    public Feedback FeedbackComment { get; set; }

    public MediaViewModel()
    {
        this.dbcontext = new MyContext(System.Configuration.ConfigurationManager.ConnectionStrings["MyContext"].ConnectionString);
        this.contentRepository = new Repository<Content>(dbcontext);
        this.feedbackRepository = new Repository<Feedback>(dbcontext);
        this.dbcontext.Configuration.LazyLoadingEnabled = true;

        //Retrieve a list of Screencasts
        Screencasts = contentRepository
            .Get(c => c.ContentTypeID == 1);

        //Retrieve a list of Podcasts
        Podcasts = contentRepository
            .Get(c => c.ContentTypeID == 2);

        //Retrieve a list of Documents
        Documents = contentRepository
            .Get(c => c.ContentTypeID == 3);

    }

    public MediaViewModel(int id)
    {
        this.dbcontext = new MyContext(System.Configuration.ConfigurationManager.ConnectionStrings["MyContext"].ConnectionString);
        this.contentRepository = new Repository<Content>(dbcontext);
        this.feedbackRepository = new Repository<Feedback>(dbcontext);
        this.dbcontext.Configuration.LazyLoadingEnabled = true;

        //Retrieve a list of Screencasts
        Screencasts = contentRepository
            .Get(c => c.ContentTypeID == 1);

        //Retrieve a list of Podcasts
        Podcasts = contentRepository
            .Get(c => c.ContentTypeID == 2);

        //Retrieve a list of Documents
        Documents = contentRepository
            .Get(c => c.ContentTypeID == 3);                       

        //Retrieve selected screencast
        SelectedMediaItem = contentRepository
            .Get(c => c.ContentID == id)
            .FirstOrDefault();              

    }

}

Solution

  • Just to add something to the answer from Mattias:

    1. He's right - if you want some information where to redirect place it in a hidden field or add it as parameter to the Form-Method.

    2. The pattern, which is common in MVC is PRG (Post/Redirect/Get) so essentially your POST should answer with a RedirectToAction("Screencast") instead of view. (http://en.wikipedia.org/wiki/Post/Redirect/Get)

    To use this you should refactor your functions a little bit:

    public ActionResult Screencast(int ID)
    {                        
        mvm = new ViewModels.MediaViewModel(ID); //The id is in the viewmodel i think
        return View(mvm);
    }
    
    
    [HttpPost] 
    //Even if unused include the formcollection for cases where the GET has the same signature
    public ActionResult Screencast(int ID, MediaViewModel mvm, FormCollection collection)
    {
        //Do something
        return RedirectToAction("Screencast", new { id = ID });
    }
    

    And inside your view:

    @using(Html.BeginForm("Screencast", new { id = Model.SelectedMediaItem })) 
    { ... }
    
    • It might be that the suggested overload needs more parameters, but you will see that ...