-3

So I fixed the issue from a previous question I asked here, now I am getting what I was looking for but when I hit save it does nothing? No error or postback and nothing saved to the database? The button doesn't do anything in my view?

I have a many to many relationship with Post and Tag in my EF Model. I am trying to assign a post to a tag. I was following this tutorial. However the button does nothing when I click save.

PostController:

public ActionResult EditPostTag(int id = 12) // hardcoded the post for now
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    var postsViewModel = new PostsViewModel
    {
        posts = db.Posts.Include(i => i.Tags).First(i => i.Id == id),
    };

    if (postsViewModel.posts == null)
        return HttpNotFound();

    var tagsList = db.Tags.ToList();
    postsViewModel.Tags = tagsList.Select(o => new SelectListItem
    {
        Text = o.Name,
        Value = o.Id.ToString()
    });

    ViewBag.UserID =
            new SelectList(db.BlogUsers, "UserID", "Email", postsViewModel.posts.Id);

    return View(postsViewModel);
} 
Community
  • 1
  • 1
G Gr
  • 6,030
  • 20
  • 91
  • 184

1 Answers1

4

Your view does not have a form tag

@model MyBlogger.ViewModel.PostsViewModel
@{
    ViewBag.Title = "EditPostTag";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>EditPostTag</h2>
@using (Html.BeginForm()) // add this
{
    ....
}

Edit (further to comments and some misunderstanding by OP)

Using a view model as you have done is always good practice however you are not taking advantage of it by continuing to use ViewBag and using it to hold a data model instead of including just the properties you need to the view. I recommend it be

public class PostViewModel // its for a single post - not plural?
{
  public int ID { get; set; }
  [Required(ErrorMessage = "Please enter a title")]
  public string Title { get; set; }
  [Display(Name = "Tags")] // plus [Required] is at least one tag must be selected
  public List<int> SelectedTags { get; set; }
  public SelectList TagsList { get; set; }
  // Its unclear if you really need the following 2 properties (see notes)
  [Display(Name = "User")]
  [Required(ErrorMessage = "Please select a user")]
  public int UserID { get; set; }
  public SelectList UserList { get; set; }
}

Side note: Its a bit unclear why you are allowing the user to select another user to be associated with the Post object. I suspect that when you save the Post you should be just assigning the current user in the controllers POST method

Your controller methods would then be (assume this is PostController)

public ActionResult Edit(int id)
{
  Post post = db.Posts.Include(i => i.Tags).FirstOrDefault(i => i.Id == id); // First() will throw an exception is the item is not found
  if (post == null) { return HttpNotFound(); }
  PostViewModel model = new PostViewModel()
  {
    ID = post.ID,
    Title = post.Title,
    SelectedTags = post.Tags.Select(t => t.Id)
  }; // include UserId property?
  ConfigureEditModel(model);
  return View(model);
}

[HttpPost]
public ActionResult Edit(PostViewModel model)
{
  if (!ModelState.IsValid)
  {
    ConfigureEditModel(model);
    return View(model);
  }
  // map your view model to a data model, save it and redirect
}

private void ConfigureEditModel(PostViewModel model)
{
  model.TagsList = new SelectList(db.Tags, "Id", "Name");
  model.UserList = new BlogUsers(db.Tags, "UserID", "Email"); // ??
}

Side note: Either SelectList or IEnumerable<SelectListItem> is acceptable (I find SelectList easier to read but its a millisecond or two slower because it uses reflection to generate the IEnumerable<SelectListItem>) but there is no point using the 4th parameter as you did with new SelectList(db.BlogUsers, "UserID", "Email", postsViewModel.posts.Id); - your binding to a property and the selected item will be the value of the property, so trying to set the Selected property is just ignored)

And finally the view (simplified to show only helpers without html attributes)

@model MyBlogger.ViewModel.PostViewModel
@using (Html.BeginForm())
{
  @Html.ValidationSummary(true)
  // @Html.HiddenFor(model => model.posts.Id) not required
  @Html.LabelFor(m => m.Title)
  @Html.TextBoxFor(m => m.Title)
  @Html.ValidationMessageFor(m => m.Title)
  // Is the UserId property required?
  @Html.LabelFor(m => m.UserID)
  @Html.DropDownListFor(m => m.UserID, Model.UserList, "Please select")
  @Html.ValidationMessageFor(m => m.UserID)
  @Html.LabelFor(model => model.SelectedTags)
  @Html.ListBoxFor(m => m.SelectedTags, Model.TagsList)
  // Add ValidationMessageFor() if at least one should be selected
  <input type="submit" value="Save" class="btn btn-default" />
}

Side note: Since the parameter of your method is named id, the value of the id property will be added to the route parameters so it is not necessary to add a hidden input for the view models ID property (the DefaultModelBinder reads route values in addition to form values, so the view models ID property will be correctly bound (to 12 in you case)

  • Thanks Stephen, all that work and it still didnt work. Database isnt showing the id's in PostTagMap.... – G Gr Apr 20 '15 at 23:34
  • You need to include the `[HttpPost]` method in your question - all you have shown is GET method. It would need a signature like `[HttpPost] public ActionResult EditPostTag(PostsViewModel model)` –  Apr 20 '15 at 23:37
  • Your current signature `public ActionResult EditPostTag(int id = 12)` is not receiving the viewmodel with the values that the user inputs – zed Apr 20 '15 at 23:43
  • IMHO that's not a good tutorial and has some practices, but its actually in the tutorial - at the bottom just before the "Conclusion" heading –  Apr 20 '15 at 23:43
  • I added in `[HttpPost] public ActionResult EditPostTag(PostsViewModel model){ return View(model) }` and I get `The ViewData item that has the key 'posts.BlogUser.UserID' is of type 'System.Guid' but must be of type 'IEnumerable` when I click save.... – G Gr Apr 20 '15 at 23:47
  • I couldn't find many tutorials on working with many to many relationships in mvc and EF. – G Gr Apr 20 '15 at 23:48
  • 1
    That's because you have not reassigned the values of the `SelectLists` and the `Tags` property (as you did in the GET method) before you return the view. Give me an hour or so and I will update my answer with the code I would recommend –  Apr 20 '15 at 23:48
  • Hey just to let you know @StephenMuecke I managed it. It was in the comments section for the httppost method, just had to change a few things but it works now! – G Gr Apr 21 '15 at 00:29
  • @GarrithGraham, Have edited the answer with some suggestions and explanations that hopefully will help with your understanding. –  Apr 21 '15 at 00:55
  • Hi Stephen, yeah I was going to try fix the user being able to see other users. I also have an issue with the int id, if I set it to int? id = 0 it returns nothing. This wasn't suppose to be an "Edit" originally it was me trying to work out how I could add a tag to a post when a user first creates a post. However I have now went down a confusing path and cant work out how to achieve this. My Create Post View is bound to the Post Model so I cant add in tags... – G Gr Apr 21 '15 at 01:12
  • Not sure I understand which bit your struggling with. The method for creating a new post would be `public ActionResult Create() { PostViewModel model = new PostViewModel(); ConfigureEditModel(model); return View(model); }` (no parameter), and then in the corresponding POST method you would map the properties to a new data model and insert it in the database –  Apr 21 '15 at 03:24