0

I am trying to implement a check box list in MVC for a many-to-many relationship (Article - Category). I have tried this, but I does not work. Does anybody know a different and properly approach to get it.

In the domain model:

public class Article
{

    public int Id { get; set; }


    [Required]
    [StringLength(255)]
    public string Title { get; set; }

    [Required]
    public string Body { get; set; }

    [Required]
    public DateTime DateCreated { get; set; }

    [Required]
    [StringLength(255)]
    public string Author { get; set; }


    public ICollection<ArticleComment> Comments { get; set; }
    public ICollection<Category> Categories { get; set; }

}

In the View Model:

  public class ArticlesCategoriesViewModel
{

    public int Id { get; set; }
    [Required]
    public string Title { get; set; }

    [Required]
    [UIHint("tinymce_jquery_full"), AllowHtml]
    public string Body { get; set; }

    [Required]
    [DataType(DataType.Date)]
    [Display(Name = "Publication Date")]
    public DateTime DateCreated { get; set; }

    [Required]
    public string Author { get; set; }

    public IEnumerable<CategoriesViewModel> Categories { get; set; }
    public IEnumerable<CategoriesViewModel> AllCategories { get; set; }
    public string[] PostedCategories { get; set; }
}

In the controller:

 public ActionResult Edit(int id)
    {
        //Return article with its categories
        Article articleToEdit = _repo.GetArticleCategories(id);   
        Mapper.CreateMap<Article, ArticlesCategoriesViewModel>();
        Mapper.CreateMap<Category, CategoriesViewModel>();     
        ArticlesCategoriesViewModel article = Mapper.Map<Article, ArticlesCategoriesViewModel>(articleToEdit);
        //Return all categories
        IEnumerable<Category> allCategories = _repo.GetAllCategories(); 
        IEnumerable<CategoriesViewModel> AllCategories = Mapper.Map <IEnumerable<Category>, IEnumerable<CategoriesViewModel>>(allCategories);

       article.AllCategories = AllCategories;

        if (articleToEdit == null)
        {
            return HttpNotFound();
        }

        return View(article);
    }

In the view model:

<ul>
    @foreach (var g in Model.AllCategories)
    {
        <li>
            <input type="checkbox" name="PostedCategories" value="@g.Id" id="@g.Id"
                 @{if (Model.Categories.FirstOrDefault(h => h.Id == g.Id) != null) { <text> checked='checked' </text>    } } />
            <label for="@g.Id">@g.Name</label>
        </li>
    }
</ul>

It works to show all the categories of the article, but when I Submit to POST my new categories selected, my model is not valid.

This is my Post method:

  [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include = "Id,Title,Body,DateCreated,Author,PostedCategories")]ArticlesCategoriesViewModel articleToEdit)
    {

        if (ModelState.IsValid)
        {
            Mapper.CreateMap<ArticlesCategoriesViewModel, Article>();
            Article article = Mapper.Map<ArticlesCategoriesViewModel, Article>(articleToEdit);

            if (_repo.EditArticle(article) && _repo.Save())
            {
                return RedirectToAction("Index");
            }

            else
            {
                ModelState.AddModelError("", "One or more erros were found. Operation was not valid.");
                return View(articleToEdit);
            }
        }

        ModelState.AddModelError("", "One or more erros were found. Model-binding operation was not valid.");
        return View(articleToEdit);

    }

I have got null references error when MVC model-data-binding try to match the data, apparently happens when it is matching the collections "AllCategories", "Categories" of my model view. I would really appreciate any help.

4
  • Are you trying to select one or more categories associated with an article? Commented Apr 28, 2015 at 2:18
  • I am trying to show to the user all the categories available and to show selected all categories associated with the article. So the user can select new ones or unchecked old ones. Commented Apr 28, 2015 at 2:54
  • You really need to use a proper view model for Categories. What is your current CategoriesViewModel? Commented Apr 28, 2015 at 3:05
  • well, my CategoriesViewModel is actullay used to receive my collection of categories associated to an article as you can see in ArticlesCategoriesViewModel and to define another collection inside of the same class which can receive all list of categories available. After that I try in the view to loop in the full collection of categories and to see if each element is in the collection of my associated categories to show it as selected. Well, If there is another approach it is welcome. Commented Apr 28, 2015 at 4:44

1 Answer 1

2

You should use a view model for Category which has properties that describe what you want to display/edit in the view

public class CategoryVM
{
  public int ID { get; set; }
  public string Name { get; set; }
  public bool IsSelected { get; set; }
}

public class ArticlesCategoriesViewModel
{
  public int Id { get; set; }
  ....
  public string Author { get; set; }
  List<CategoryVM> Categories { get; set; }
}

View

for(int i = 0; i < Model.Categories.Count; i++)
{
  @Html.CheckBoxFor(m => m.Categories[i].IsSelected)
  @Html.LabelFor(m => m.Categories[i].IsSelected, Model.Categories[i].Name)
  @Html.HiddenFor(m => m.Categories[i].ID)
  @Html.HiddenFor(m => m.Categories[i].Name)
}

In the GET method, initialize the ArticlesCategoriesViewModel based on the id, and add a CategoryVM for each available category and set the IsSelected property based on the categories already assigned to the Article. The in the POST method, you can get the selected categories with var selectedCategories = articleToEdit.Categories.Where(c => c.IsSelected).

You should also remove the [Bind(Include="..")] attribute. A view model represents only what you display/edit in a view so it is unnecessary since you should not be excluding any properties.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks Stephen. I have implemented your approach. It worked properly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.