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.
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.
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
:
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:
<!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
.
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:
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:
<option>
elements: a list of categories from our database.<option>
element: the Category
's CategoryId
.<option>
element: the name of the Category
. Item
Create and Edit ViewsNext, let's update the corresponding views:
@{
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>
@{
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.
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.
Lesson 35 of 36
Last updated February 28, 2022