Lesson Wednesday

Now that our one-to-many relationship is set up, let's update the rest of our application so that our users can actually associate a Category with an Item. In the process, we'll also learn about a place to store data called a ViewBag.

ViewBag Syntax


We already know how to send a model to our views, but sometimes we need additional data that isn't a part of our model. We can store that data in a ViewBag.

A ViewBag is a way to send temporary data from a controller to a view. Every MVC Razor view has a ViewBag. We can declare a property of the ViewBag in a route and it will be available to us in the view. Here's an example:

public ActionResult Index()
{
  ViewBag.MyFavoriteColor = "green";
  return View();
}

This will assign the string "green" to the MyFavoriteColor property of the ViewBag. We don't have to instantiate the ViewBag or create a new class. It's simply made available to the view.

To reference the ViewBag data in the view, we can use this expression:

<p>I like the color @ViewBag.MyFavoriteColor.</p>

The user would see the following:

I like the color green.

Using ViewBag to Add Custom Titles to Webpages


We can use ViewBag to add custom titles to our webpages. We'll learn how to do this now, but you don't have to include this functionality in your projects or on any independent project.

Check out this code from the Index() route of the ItemsController:

Controllers/ItemsController.cs
public ActionResult Index()
{
  List<Item> model = _db.Items.Include(item => item.Category).ToList(); 
  ViewBag.PageTitle = "View All Items";
  return View(model);
}

We've added a new line: ViewBag.PageTitle = "View All Items";. By creating a property of the ViewBag called PageTitle, we can then access it in our _Layout.cshtml file to set the value of the HTML element <title>. See the updated _Layout.cshtml below:

Views/Shared/_Layout.cshtml
<!DOCTYPE html>
<html>
  <head>
    ...
    <title>@ViewBag.PageTitle</title>
    ...
  </head>
  ...  
</html>

Now when I navigate to the Item's Index view, the title of the webpage will be "View All Items". Take note, if you do this for one page, you should do it for all pages. If there is no PageTitle property or value, the title will default to the current URL. For example, if we're on the Index View of the HomeController, the title would state localhost:5000.

Using ViewBag for Category Data in the ItemsController


Let's now use ViewBag to get Category data into our Item Views. We will be updating our example To Do List project with the following changes. In this example, we'll access ViewBag data from an HTML helper, and the syntax in the View will look different than what we've seen so far.

We will need Category data in two of our Item routes, so let's update those two methods in ItemsController so that they use ViewBag. Here are the two methods we need to update:

Controllers/ItemsController.cs
using Microsoft.AspNetCore.Mvc.Rendering;

...

public ActionResult Create()
{
  ViewBag.CategoryId = new SelectList(_db.Categories, "CategoryId", "Name");
  return View();
}

...

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

...

First we add the necessary using directive so that we have access to SelectList. Now note that both methods have the following lines added to them:

ViewBag.CategoryId = new SelectList(_db.Categories, "CategoryId", "Name");

When we create and edit our items, we want them to belong to categories that already exist. We do this by creating a ViewBag.CategoryId property in the Create and Edit GET actions, and assigning it as a new SelectList object.

A SelectList will provide a list of the data needed to create an html <select> list of all the categories from our database. The displayed text of each <option> will be the Category's Name property, and the value of the <option> will be the Category's CategoryId. That way, a user can select an Category from the dropdown to associate with the Item we are creating or editing.

The SelectList takes multiple arguments:

  • The first argument represents the data that will populate our SelectList's <option> elements: a list of categories from our database.
  • The second argument is the value of the every <option> element: the Category's CategoryId.
  • The third argument is the displayed text of every <option> element: the name of the Category.

Updating Item Create and Edit Views

Next, let's update the corresponding views:

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

@model ToDoList.Models.Item

<h4>Add a new task</h4>

@using (Html.BeginForm())
{
  @Html.LabelFor(model => model.Description)
  @Html.TextBoxFor(model => model.Description)

  @Html.LabelFor(model => model.Category)
  @Html.DropDownList("CategoryId")

  <input type="submit" value="Add new item" class="btn btn-default" />
}
<p>@Html.ActionLink("Show all items", "Index")</p>
Views/Items/Edit.cshtml
@{
  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.LabelFor(model => model.Category)
  @Html.DropDownList("CategoryId")

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

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

Note that we've added the following two lines to both views:

@Html.LabelFor(model => model.Category)
@Html.DropDownList("CategoryId")

We use an HTML helper method called DropDownList(), we give DropDownList() the string name of a ViewBag property of the type SelectList. Since we saved our SelectList as ViewBag.CategoryId, we give DropDownList() the string "CategoryId". This helper method will create an html dropdown select list out of the SelectList object.

Note - CategoryId may seem like a bad name for a SelectList object, but the reason we chose that name is because that will be the name attribute on the <select> tag created. You can check this in your browser DevTools Inspector and see <select id="CategoryId" name="CategoryId">. We could use a different name like ViewBag.CategoryList and you'd get <select id="CategoryList" name="CategoryList">. The problem with that is when we submit our form it will use the name attribute to know what property of the Item object to assign a value, so it will try to create an Item with a CategoryList property, which will not work.

At this point, we've successfully set up a one-to-many relationship. We can run our application and make an association between an Item and a Category via a dropdown. Note that you may need to manually add a Category in the database to see this work.

It can be easy to overlook what we've done utilizing lazy loading. It might be unclear where that's even happening! We can see the effect by looking at what we don't need to include in the categories controller. Our categories lazily load their items, so we don't need any kind of Include statement in the Category Details method. Instead, we can just call selectedCategory.Items to access those values.

For further information on using Entity, check out Microsoft's documentation on Entity Framework.

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 branch in the repository.

Example GitHub Repo for To Do List

Lesson 35 of 36
Last updated February 28, 2022