user3007593
user3007593

Reputation: 43

CheckBoxList for many-to-many relationship in ASP.NET MVC

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.

Upvotes: 0

Views: 1185

Answers (1)

user3559349
user3559349

Reputation:

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.

Upvotes: 2

Related Questions