ViewBag passes small amounts of unstructured data to an ASP.NET Core MVC view alongside its view model. A controller action inherits the ViewBag property from Controller; its dynamic object gains properties when you assign values to them. The view reads those values through its own ViewBag property. section 22.2
Add a price comparison to a product view
In HomeController.Index, assign `ViewBag.AveragePrice = await context.Products.AverageAsync(p => p.Price);`, then return the requested Product as the view model. In Views/Home/Index.cshtml, keep `@model Product?` and use `ViewBag.AveragePrice` in an expression that displays the product price as a percentage of the average. The extra value does not require a new view model class. section 22.2
Use ViewBag for a little supplementary data, not as a substitute for a considered view model. Dynamic properties are not checked by the compiler. If multiple actions use the same property, or one action adds more than two or three properties, create a view model class instead. section 22.2
Highlight the selected navigation category
A navigation view component can set `ViewBag.SelectedCategory = RouteData?.Values["category"]` while passing its category list as the view model. Its Razor view compares each category with `ViewBag.SelectedCategory` to choose a CSS class for the selected link. A unit test can supply route data through ViewComponentContext and read the selected value from the resulting ViewViewComponentResult. section 8.1.3
A view can also set a ViewBag property for its layout. For example, set `ViewBag.Title = "Product Table"` in the view and read it in `_Layout.cshtml`. A value assigned in the view takes precedence over one assigned by the action. To preserve the action's value, use `ViewBag.Title = ViewBag.Title ?? "Product Table"`; in the layout, provide a fallback when a view might not define Title. section 22.4.1
Listing 22.5 Using the view bag in the HomeController.cs file in the Controllers folder
using Microsoft.AspNetCore.Mvc;
using WebApp.Models;
using Microsoft.EntityFrameworkCore;
namespace WebApp.Controllers {
public class HomeController : Controller {
private DataContext context;
public HomeController(DataContext ctx) {
context = ctx;
}
public async Task<IActionResult> Index(long id = 1) {
ViewBag.AveragePrice =
await context.Products.AverageAsync(p => p.Price);
return View(await context.Products.FindAsync(id));
}
public IActionResult List() {
return View(context.Products);
}
}
}
The controller listing shows an action assigning AveragePrice before returning a Product view model. section 22.2
Listing 22.6 Using the view bag in the Index.cshtml file in the Views/Home folder
@model Product?
<!DOCTYPE html>
<html>
<head>
<link href="/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>
<h6 class="bg-primary text-white text-center m-2 p-2">
Product Table
</h6>
<div class="m-2">
<table class="table table-sm table-striped table-bordered">
<tbody>
<tr><th>Name</th><td>@Model?.Name</td></tr>
<tr>
<th>Price</th>
<td>
@Model?.Price.ToString("c")
(@(((Model?.Price / ViewBag.AveragePrice)
* 100).ToString("F2"))% of average price)
</td>
</tr>
<tr><th>Category ID</th><td>@Model?.CategoryId</td></tr>
</tbody>
</table>
</div>
</body>
</html>
The matching Razor listing shows the view model and ViewBag value used in the same price display. section 22.2
