The unit test for our ability to produce a category list is relatively simple. Our goal is to create a list that is sorted in alphabetical order and contains no duplicates. The simplest way to do this is to supply some test data that does have duplicate categories and that is not in order, pass this to the NavController, and assert that the data has been properly cleaned up. Here is the unit test we used:
[TestMethod]
public void Can_Create_Categories() { // Arrange
// - create the mock repository
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => m.Products).Returns(new Product[] {
new Product {ProductID = 1, Name = "P1", Category = "Apples"}, new Product {ProductID = 2, Name = "P2", Category = "Apples"}, new Product {ProductID = 3, Name = "P3", Category = "Plums"},
new Product {ProductID = 4, Name = "P4", Category = "Oranges"}, }.AsQueryable());
// Arrange - create the controller
NavController target = new NavController(mock.Object);
// Act = get the set of categories
string[] results = ((IEnumerable<string>)target.Menu().Model).ToArray();
// Assert
Assert.AreEqual(results.Length, 3);
Assert.AreEqual(results[0], "Apples");
Assert.AreEqual(results[1], "Oranges");
Assert.AreEqual(results[2], "Plums");
}
We created a mock repository implementation that contains repeating categories and categories that are not in order. We assert that the duplicates are removed and that alphabetical ordering is imposed.
Creating the Partial View
Since the navigation list is just part of the overall page, it makes sense to create a partial view for the Menu action method. Right-click the Menu method in the NavController class and select Add View from the pop-up menu.
Leave the view name as Menu, check the option to create a strongly typed view, and enter IEnumerable<string>
as the model class type, as shown in Figure 8-3.
Figure 8-3. Creating the Menu partial view
Check the option to create a partial view. Click the Add button to create the view. Edit the view contents so that they match those shown in Listing 8-8.
8. Listing 8-8. The Menu Partial View
@model IEnumerable<string>
@{
Layout = null;
}
@Html.ActionLink("Home", "List", "Product")
@foreach (var link in Model) { @Html.RouteLink(link, new { controller = "Product",
action = "List", category = link, page = 1 }) }
We’ve added a link called Home that will appear at the top of the category list and will take the user back to the first page of the list of all products with no category filter. We did this using the ActionLink helper method, which generates an HTML anchor element using the routing information we configured earlier.
We then enumerated the category names and created links for each of them using the RouteLink method. This is similar to ActionLink, but it lets us supply a set of name/value pairs that are taken into account when generating the URL from the routing configuration. Don’t worry if all this talk of routing doesn’t make sense yet—we explain everything in depth in Chapter 11.
The links we generate will look pretty ugly by default, so we’ve defined some CSS that will improve their appearance. Add the styles shown in Listing 8-9 to the end of the Content/Site.css file in the SportsStore.WebUI project.
9. Listing 8-9. CSS for the Category Links DIV#categories A
{
font: bold 1.1em "Arial Narrow","Franklin Gothic Medium",Arial; display: block;
text-decoration: none; padding: .6em; color: Black;
border-bottom: 1px solid silver;
}
DIV#categories A.selected { background-color: #666; color: White; } DIV#categories A:hover { background-color: #CCC; }
DIV#categories A.selected:hover { background-color: #666; }
You can see the category links if you run the application, as shown in Figure 8-4. If you click a category, the list of items is updated to show only items from the selected category.
Figure 8-4. The category links
Highlighting the Current Category
At present, we don’t indicate to users which category they are viewing. It might be something that the customer could infer from the items in the list, but it is preferable to provide some solid visual feedback.
We could do this by creating a view model that contains the list of categories and the selected category, and in fact, this is exactly what we would usually do. But instead, we are going to demonstrate the View Bag feature we mentioned in the Razor section of Chapter 5. This feature allows us to pass data from the controller to the view without using a view model. Listing 8-10 shows the changes to the Menu action method.
10. Listing 8-10. Using the View Bag Feature public ViewResult Menu(string category = null) { ViewBag.SelectedCategory = category;
IEnumerable<string> categories = repository.Products .Select(x => x.Category)
.Distinct() .OrderBy(x => x);
return View(categories);
}
We’ve added a parameter to the Menu action method called category. The value for this parameter will be provided automatically by the routing configuration. Inside the method body, we’ve dynamically created a SelectedCategory property in the ViewBag object and set its value to be the parameter value. In Chapter 5, we explained that ViewBag is a dynamic object, and we can create new properties simply by setting values for them.