The behavior that we care about for the Index method is that it correctly returns the Product objects that are in the repository. We can test this by creating a mock repository implementation and comparing the test data with the data returned by the action method. Here is the unit test:
[TestMethod]
public void Index_Contains_All_Products() { // 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"}, new Product {ProductID = 2, Name = "P2"}, new Product {ProductID = 3, Name = "P3"},
}.AsQueryable());
// Arrange - create a controller
AdminController target = new AdminController(mock.Object);
// Action
Product[] result = ((IEnumerable<Product>)target.Index().ViewData.Model).ToArray();
// Assert
Assert.AreEqual(result.Length, 3);
Assert.AreEqual("P1", result[0].Name);
Assert.AreEqual("P2", result[1].Name);
Assert.AreEqual("P3", result[2].Name);
}
Creating a New Layout
We are going to create a new Razor layout to use with the SportsStore administration views. This will be a simple layout that provides a single point where we can apply changes to all of the administration views.
To create the layout, right-click the Views/Shared folder in the SportsStore.WebUI project and select Add New Item. Select the MVC 3 Layout Page (Razor) template and set the name to _AdminLayout.cshtml, as shown in Figure 9-3. Click the Add button to create the new file.
Figure 9-3. Creating a new Razor layout
The convention is to start the layout name with an underscore (_). Razor is also used by another Microsoft technology called WebMatrix, which uses the underscore to prevent layout pages from being served to browsers.
MVC doesn’t need this protection, but the convention for naming layouts is carried over to MVC applications anyway.
We want to create a reference to a CSS file in the layout, as shown in Listing 9-4.
4. Listing 9-4. The _AdminLayout.cshtml File
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Admin.css")" rel="stylesheet" type="text/css" />
</head>
<body>
<div>
@RenderBody() </div>
</body>
</html>
The addition (shown in bold) is a reference to a CSS file called Admin.css in the Content folder. To create the Admin.css file, right-click the Content folder, select Add New Item, select the Style Sheet template, and set the name to Admin.css, as shown in Figure 9-4.
Figure 9-4. Creating the Admin.css file
Replace the contents of the Admin.css file with the styles shown in Listing 9-5.
5. Listing 9-5. The CSS Styles for the Admin Views BODY, TD { font-family: Segoe UI, Verdana } H1 { padding: .5em; padding-top: 0; font-weight: bold;
font-size: 1.5em; border-bottom: 2px solid gray; } DIV#content { padding: .9em; }
TABLE.Grid TD, TABLE.Grid TH { border-bottom: 1px dotted gray; text-align:left; } TABLE.Grid { border-collapse: collapse; width:100%; }
TABLE.Grid TH.NumericCol, Table.Grid TD.NumericCol { text-align: right; padding-right: 1em; }
FORM {margin-bottom: 0px; }
DIV.Message { background: gray; color:White; padding: .2em; margin-top:.25em; } .field-validation-error { color: red; display: block; }
.field-validation-valid { display: none; }
.input-validation-error { border: 1px solid red; background-color: #ffeeee; } .validation-summary-errors { font-weight: bold; color: red; }
.validation-summary-valid { display: none; }
Implementing the List View
Now that we have created the new layout, we can add a view to the project for the Index action method of the Admin controller. Right-click inside the Index method and select Add View from the pop-up menu. Set the name of the view to Index, as shown in Figure 9-5.
Figure 9-5. Creating the Index view
8
We are going to use a scaffold view, which is where Visual Studio looks at the class we select for a strongly typed view and creates a view containing markup tailored for that model type. To do this, select Product from the list of model classes and List for the scaffold template, as shown in Figure 9-5.
Note When using the List scaffold, Visual Studio assumes you are working with an IEnumerable sequence of the model view type, so you can just select the singular form of the class from the list.
We want to apply our newly created layout, so check the option to use a layout for the view and select the _AdminLayout.cshtml file from the Views/Shared folder. Click the Add button to create the view. The scaffold view that Visual Studio creates is shown in Listing 9-6.
6. Listing 9-6. The Scaffold for List Views
@model IEnumerable<SportsStore.Domain.Entities.Product>
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th></th>
<th>Name</th>
<th>Description</th>
<th>Price</th>
<th>Category</th>
</tr>
@foreach (var item in Model) { <tr>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ProductID }) | @Html.ActionLink("Details", "Details", new { id=item.ProductID }) | @Html.ActionLink("Delete", "Delete", new { id=item.ProductID }) </td>
<td>@item.Name</td>
<td>@item.Description</td>
<td>@String.Format("{0:F}", item.Price)</td>
<td>@item.Category</td>
</tr>
}
</table>
You can see how this view is rendered by requesting the Admin/Index URL from the application, as shown in Figure 9-6.
Figure 9-6. Rendering the scaffold List view
The scaffold view does a pretty good job of setting things up for us. We have columns for each of the properties in the Product class and links for other CRUD operations that refer to action methods in the same controller. That said, the markup is a little verbose. Also, we want something that ties in with the CSS we created earlier. Edit your Index.cshtml file to match Listing 9-7.
7. Listing 9-7. Modifying the Index.cshtml View
@model IEnumerable<SportsStore.Domain.Entities.Product>
@{
ViewBag.Title = "Admin: All Products";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<h1>All Products</h1>
<table class="Grid">
<tr>
<th>ID</th>
<th>Name</th>
<th class="NumericCol">Price</th>
<th>Actions</th>
</tr>
@foreach (var item in Model) { <tr>
<td>@item.ProductID</td>
<td>@Html.ActionLink(item.Name, "Edit", new { item.ProductID })</td>
<td class="NumericCol">@item.Price.ToString("c")</td>
<td>
@using (Html.BeginForm("Delete", "Admin")) { @Html.Hidden("ProductID", item.ProductID) <input type="submit" value="Delete"/>
} </td>
</tr>
}
</table>
<p>@Html.ActionLink("Add a new product", "Create")</p>
This view presents the information in a more compact form, omitting some of the properties from the Product class and using a different approach to lay out the links to specific products. You can see how this view renders in Figure 9-7.
Figure 9-7. Rendering the modified Index view
Now we have a nice list page. The administrator can see the products in the catalog, and there are links or buttons to add, delete, and inspect items. In the following sections, we’ll add the functionality to support each of these features.
Editing Products
To provide create and update features, we will add a product-editing page similar to the one shown in Figure 9-1.
There are two halves to this job:
Display a page that will allow the administrator to change values for the properties of a product.
Add an action method that can process those changes when they are submitted.
Creating the Edit Action Method
Listing 9-8 shows the Edit method we have added to the AdminController class. This is the action method we specified in the calls to the Html.ActionLink helper method in the Index view.
8. Listing 9-8. The Edit Method public ViewResult Edit(int productId) {
Product product = repository.Products.FirstOrDefault(p => p.ProductID == productId);
return View(product);
}
This simple method finds the product with the ID that corresponds to the productId parameter and passes it as a view model object.