Lesson Weekend

We've added create and read functionality to our ItemsController. Now we're ready to add update functionality.

UPDATE: Updating Edit()


In addition to being able to edit the item's description, let's add the ability to associate additional categories to the item. After all, our application wouldn't have true many-to-many functionality if we don't provide users with the ability to make that many-to-many association. We will only add this functionality in the ItemsController because that will fulfill both sides of the many-to-many relationship. An item will be able to have many categories and vice versa.

Updating the Edit Route & View

First, let's go ahead and uncomment the Edit() GET and POST routes in our items controller. They should look like this:

public ActionResult Edit(int id)
{
    var thisItem = _db.Items.FirstOrDefault(item => item.ItemId == id);
    ViewBag.CategoryId = new SelectList(_db.Categories, "CategoryId", "Name");
    return View(thisItem);
}

[HttpPost]
public ActionResult Edit(Item item)
{
    _db.Entry(item).State = EntityState.Modified;
    _db.SaveChanges();
    return RedirectToAction("Index");
}

Let's also uncomment the code in Views/Items/Edit.cshtml and make a minor update:

@{
  Layout = "_Layout";
}

@model ToDoList.Models.Item

<h2>Edit</h2>

<h4>Edit this task: @Html.DisplayFor(model => model.Description)</h4>

@using (Html.BeginForm())
{
    @Html.HiddenFor(model => model.ItemId)

    @Html.LabelFor(model => model.Description)
    @Html.EditorFor(model => model.Description)

    @Html.Label("Select category")
    @Html.DropDownList("CategoryId")

    <input type="submit" value="Save" />
}

<p>@Html.ActionLink("Back to list", "Index")</p>

And finally, we'll tailor our Edit() POST route to mirror some of the functionality of our Create() route.

Controllers/ItemsController.cs
    [HttpPost]
    public ActionResult Edit(Item item, int CategoryId)
    {
      if (CategoryId != 0)
      {
        _db.CategoryItem.Add(new CategoryItem() { CategoryId = CategoryId, ItemId = item.ItemId });
      }
      _db.Entry(item).State = EntityState.Modified;
      _db.SaveChanges();
      return RedirectToAction("Index");
    }

Notice that we again use a conditional in the case that no Categories yet exist or are being used.

Adding an AddCategory() route

We'll now implement the many-to-many portion of the relationship by adding the ability to add a category to an item any given number of times.

Let's create an AddCategory() route to our items controller as well as a corresponding view:

public ActionResult AddCategory(int id)
{
    var thisItem = _db.Items.FirstOrDefault(item => item.ItemId == id);
    ViewBag.CategoryId = new SelectList(_db.Categories, "CategoryId", "Name");
    return View(thisItem);
}
Views/Items/AddCategory.cshtml
@{
  Layout = "_Layout";
}

@model ToDoList.Models.Item

<h2>Add a category</h2>

<h4>Add a category to this task: @Html.DisplayFor(model => model.Description)</h4>

@using (Html.BeginForm())
{
    @Html.HiddenFor(model => model.ItemId)

    @Html.Label("Select category")
    @Html.DropDownList("CategoryId")

    <input type="submit" value="Save" />
}

<p>@Html.ActionLink("Back to list", "Index")</p>

We'll now need a POST route to process the form submission:

[HttpPost]
public ActionResult AddCategory(Item item, int CategoryId)
{
    if (CategoryId != 0)
    {
      _db.CategoryItem.Add(new CategoryItem() { CategoryId = CategoryId, ItemId = item.ItemId });
      _db.SaveChanges();
    }
    return RedirectToAction("Index");
}

This route should be review and will look like our Edit() POST route but without the portion relating to changing the state of the item in question.

It may seem redundant to have a separate page where we add a categories because we already have the ability to add a category in our edit view. However, in that case, we had only established a one-to-many relationship as we never gave the user the ability to add more than one category to an item at a time, only the ability to edit an item's category. By adding a separate route to add categories, we give the user the option of adding many categories to an item.

Of course, we'll need to add a link to this view. Let's add the link to the details view file:

<p>@Html.ActionLink("Add a Category", "AddCategory", new { id = Model.ItemId })</p>

Categories

Let's now quickly connect the Categories side of the application so that we can see our new many-to-many relationship in action.

Go ahead and uncomment all of the code in CategoriesController.cs and in all of the views in Views/Categories.

We'll have to make some changes to our controller and views to account for our new many-to-many relationship. Take some time to see if you can implement these changes successfully on your own before referencing the code below.

Once you've finished, compare your changes with the following:

CategoriesController.cs
public ActionResult Details(int id)
{
    var thisCategory = _db.Categories
        .Include(category => category.JoinEntities)
        .ThenInclude(join => join.Item)
        .FirstOrDefault(category => category.CategoryId == id);
    return View(thisCategory);
}
Views/Categories/Details.cshtml
@{
  Layout = "_Layout";
}

@model ToDoList.Models.Category;

<h2>Category Details</h2>
<hr />
<h3>@Html.DisplayNameFor(model => model.Name): @Html.DisplayFor(model => model.Name)</h3>

@if(@Model.JoinEntities.Count == 0)
{
  <p>This category does not contain any items</p>
}
else
{
  <h4>Items the category contains:</h4>
  <ul>
  @foreach(var join in Model.JoinEntities)
  {
    <li>@join.Item.Description</li>
  }
  </ul>
}

<p>@Html.ActionLink("Back to categories", "Index")</p>
<p>@Html.ActionLink("Edit Category", "Edit", new { id = Model.CategoryId })</p>
<p>@Html.ActionLink("Delete Category", "Delete", new { id = Model.CategoryId })</p>

Repository Reference

Follow the link below to view how a sample version of the project should look at this point. Note that this is a link to a specific commit in the repository.

Example GitHub Repo for To Do List

Lesson 7 of 14
Last updated February 24, 2022