10. Final updates to Navigation and Site Design
We’ve completed all the major functionality for our site, but we still have some features to add to the site navigation, the home page, and the Store Browse page.
Creating the Shopping Cart Summary Partial View
We want to expose the number of items in the user’s shopping cart across the entire site.
We can easily implement this by creating a partial view which is added to our Site.master.
As shown previously, the ShoppingCart controller includes a CartSummary action method which returns a partial view:
//
// GET: /ShoppingCart/CartSummary
[ChildActionOnly]
public ActionResult CartSummary()
{
var cart = ShoppingCart.GetCart(this.HttpContext);
ViewData["CartCount"] = cart.GetCount();
return PartialView("CartSummary");
}
To create the CartSummary partial view, right-click on the Views/ShoppingCart folder and select Add View. Name the view CartSummary and check the “Create a partial view” checkbox as shown below.
The CartSummary partial view is really simple - it’s just a link to the ShoppingCart Index view which shows the number of items in the cart. The complete code for CartSummary.cshtml is as follows:
@Html.ActionLink("Cart (" + ViewData["CartCount"] + ")", "Index",
"ShoppingCart",
new { id = "cart-status" })
We can include a partial view in any page in the site, including the Site master, by using the Html.RenderAction method. RenderAction requires us to specify the Action Name (“CartSummary”) and the Controller Name (“ShoppingCart”) as below.
@Html.RenderAction("CartSummary", "ShoppingCart")
Before adding this to the site Layout, we will also create the Genre Menu so we can make all of our Site.master updates at one time.
Creating the Genre Menu Partial View
We can make it a lot easier for our users to navigate through the store by adding a Genre Menu which lists all the Genres available in our store.
We will follow the same steps also create a GenreMenu partial view, and then we can add them both to the Site master. First, add the following GenreMenu controller action to the StoreController:
//
// GET: /Store/GenreMenu
[ChildActionOnly]
public ActionResult GenreMenu()
{
var genres = storeDB.Genres.ToList();
return PartialView(genres);
}
This action returns a list of Genres which will be displayed by the partial view, which we will create next.
Note: We have added the [ChildActionOnly] attribute to this controller action, which indicates that we only want this action to be used from a Partial View. This attribute will prevent the controller action from being executed by browsing to /Store/GenreMenu. This isn’t required for partial views, but it is a good practice, since we want to make sure our controller actions are used as we intend. We are also returning PartialView rather than View, which lets the view engine know that it shouldn’t use the Layout for this view, as it is being included in other views.
Right-click on the GenreMenu controller action and create a partial view named GenreMenu which is strongly typed using the Genre view data class as shown below.
Update the view code for the GenreMenu partial view to display the items using an unordered list as follows.
@model IEnumerable<MvcMusicStore.Models.Genre>
<ul id="categories">
@foreach (var genre in Model)
{
<li>@Html.ActionLink(genre.Name,
"Browse", "Store",
new { Genre = genre.Name }, null)
</li>
}
</ul>
Updating Site Layout to display our Partial Views
We can add our partial views to the Site Layout (/Views/Shared/_Layout.cshtml) by calling Html.RenderAction(). We’ll add them both in, as well as some additional markup to display them, as shown below:
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet"
type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"
type="text/javascript"></script>
</head>
<body>
<div id="header">
<h1><a href="/">ASP.NET MVC MUSIC STORE</a></h1>
<ul id="navlist">
<li class="first"><a href="@Url.Content("~")" id="current">Home</a></li>
<li><a href="@Url.Content("~/Store/")">Store</a></li>
<li>@{Html.RenderAction("CartSummary", "ShoppingCart");}</li>
<li><a href="@Url.Content("~/StoreManager/")">Admin</a></li>
</ul>
</div>
@{Html.RenderAction("GenreMenu", "Store");}
<div id="main">
@RenderBody()
</div>
<div id="footer">
built with <a href="http://asp.net/mvc">ASP.NET MVC 3</a>
</div>
</body>
</html>
Now when we run the application, we will see the Genre in the left navigation area and the Cart Summary at the top.
Update to the Store Browse page
The Store Browse page is functional, but doesn’t look very good. We can update the page to show the albums in a better layout by updating the view code (found in /Views/Store/Browse.cshtml) as follows:
@model MvcMusicStore.Models.Genre
@{
ViewBag.Title = "Browse Albums";
}
<div class="genre">
<h3><em>@Model.Name</em> Albums</h3>
<ul id="album-list">
@foreach (var album in Model.Albums)
{
<li>
<a href="@Url.Action("Details", new { id = album.AlbumId })”
<img alt="@album.Title" src="@album.AlbumArtUrl" />
<span>@album.Title</span>
</a>
</li>
}
</ul>
</div>
Here we are making use of Url.Action rather than Html.ActionLink so that we can apply special formatting to the link to include the album artwork.
Note: We are displaying a generic album cover for these albums. This information is stored in the database and is editable via the Store Manager. You are welcome to add your own artwork.
Now when we browse to a Genre, we will see the albums shown in a grid with the album artwork.
Updating the Home Page to show Top Selling Albums
We want to feature our top selling albums on the home page to increase sales. We’ll make some updates to our HomeController to handle that, and add in some additional graphics as well.
First, we’ll add a navigation property to our Album class so that EntityFramework knows that they’re associated. The last few lines of our Album class should now look like this:
public virtual Genre Genre { get; set; }
public virtual Artist Artist { get; set; }
public virtual List<OrderDetail> OrderDetails { get; set; }
}
}
Note: This will require adding a using statement to bring in the System.Collections.Generic namespace.
First, we’ll add a storeDB field and the MvcMusicStore.Models using statements, as in our other controllers. Next, we’ll add the following method to the HomeController which queries our database to find top selling albums according to OrderDetails.
private List<Album> GetTopSellingAlbums(int count)
{
// Group the order details by album and return
// the albums with the highest count
return storeDB.Albums
.OrderByDescending(a => a.OrderDetails.Count())
.Take(count)
.ToList();
}
This is a private method, since we don’t want to make it available as a controller action. We are including it in the HomeController for simplicity, but you are encouraged to move your business logic into separate service classes as appropriate.
With that in place, we can update the Index controller action to query the top 5 selling albums and return them to the view.
public ActionResult Index()
{
// Get most popular albums
var albums = GetTopSellingAlbums(5);
return View(albums);
}
The complete code for the updated HomeController is as shown below.
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using MvcMusicStore.Models;
namespace MvcMusicStore.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
MusicStoreEntities storeDB = new MusicStoreEntities();
public ActionResult Index()
{
// Get most popular albums
var albums = GetTopSellingAlbums(5);
return View(albums);
}
private List<Album> GetTopSellingAlbums(int count)
{
// Group the order details by album and return
// the albums with the highest count