Lesson Weekend

In the last lesson, we set up a many-to-many structure in our classes, created a join class, and configured and migrated our code into our database. Now we're ready to create controllers that will handle our new application structure. In this lesson, we'll focus on adding read functionality to the ItemsController.

READ: Index() and Details() Routes


In our ItemsController.cs, let's uncomment out our code up to the Index() route and then replace the contents of the route with the following:

Controllers/ItemsController.cs
public ActionResult Index()
{
    return View(_db.Items.ToList());
}

At this point, this code should look very familiar. Let's go ahead and uncomment the code in the Views/Home/Index.cshtml and Views/Items/Index.cshtml files. Let's remove the | @item.Category.Name portion of the items index view as well.

Next, let's uncomment our Details() route and add some new code:

Controllers/ItemsController.cs
public ActionResult Details(int id)
{
    var thisItem = _db.Items
        .Include(item => item.JoinEntities)
        .ThenInclude(join => join.Category)
        .FirstOrDefault(item => item.ItemId == id);
    return View(thisItem);
}

We see a new method: ThenInclude(). Let's go over what's happening here.

Our _db.Items expression gives us a list of Item objects from the database. However, if we completed the query now (using the FirstOrDefault() method), we'd simply have an Item without its related Categorys.

We need to .Include(item => item.JoinEntities) to load the JoinEntities property of each Item. However, the JoinEntities property on an Item is just a collection of join entities, each of type ICollection<CategoryItem>. These are not the actual categories related to an Item.

We need the actual Category objects themselves, so we use ThenInclude() method to load the Category of each CategoryItem. Remember that a CategoryItem is simply a reference to a relationship. Each CategoryItem includes the id of an Item as well as the id of a Category. We are actually returning the associated Category of a CategoryItem here.

Finally, our FirstOrDefault() method specifies which item from the database we're working with.

Let's now uncomment and update our Items/Details.cshtml view to display an Item's associated categories:

Views/Items/Details.cshtml
@{
  Layout = "_Layout";
}

@model ToDoList.Models.Item

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

@if(@Model.JoinEntities.Count == 0)
{
  <p>This item does not belong to any categories</p>
}
else
{
  <h4>Categories the item belongs to:</h4>
  <ul>
  @foreach(var join in Model.JoinEntities)
  {
    <li>@join.Category.Name</li>
  }
  </ul>
}

<p>@Html.ActionLink("Back to list", "Index")</p>
<p>@Html.ActionLink("Edit Item", "Edit", new { id = Model.ItemId })</p>
<p>@Html.ActionLink("Delete Item", "Delete", new { id = Model.ItemId })</p>

We've added a conditional in the case that an item has been created without a corresponding entry in the join table.

Note that the model's JoinEntities property now holds the list of associated categories. We call the variable join to specify that we are accessing those categories via the join class.

We should now be able to run our application and navigate from the homepage to the items index view. However, in order to view our new details page, we'll have to first add some items. Let's do that next.

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 5 of 14
Last updated more than 3 months ago.