Bước kế tiếp là sẽ nâng cấp Product controllers để phương thức hoạt động củaListsẽ lọc trong Product ra những sản phẩm theo danh mục và sử dụng tham số mới để xem những mẫu mà đã được ch
Trang 1Chương 8 :
Cửa hàng thể thao trực tuyến : điều hướng
Điều khiển điều hướng:
Các ứng dụng của cửa hàng thể thao trực tuyến sẽ hữu dụng hơn khi khách hàng có thể lựa chọn sản phẩm dựa theo từng mục Ở đây sẽ có ba giai đoạn:
.Nâng cao hoạt động của model List trong class
ProductControllerđể có thể chọn lọc các dụng cụ trong kho hàng
.Sử dụng lại và nâng cao các đề án về URL và rà soát lại các chiến lược định tuyến
.Tạo một danh mục mà sẽ được đưa vào trong thanh sidebar của site, làm nổi bật các danh mục và lien kết chúng với những thứ khác
public class ProductsListViewModel {
public IEnumerable<Product> Products { get; set; } public PagingInfo PagingInfo { get; set; }
public string CurrentCategory { get; set; }
} }
Trang 2CurrentCategorylà một tham số mới được thêm vào Bước kế tiếp là sẽ
nâng cấp Product controllers để phương thức hoạt động củaListsẽ lọc trong Product ra những sản phẩm theo danh mục và sử dụng tham số mới
để xem những mẫu mà đã được chọn lựa trong danh mục Điều này sẽ được thể hiện torng Listing 8-2
Listing 8-2:thêm Category Support vào phương thức hoạt động của List trong file ProcductController.cs
public class ProductController : Controller {
private IProductRepository repository;
public int PageSize = 4;
public ProductController(IProductRepository productRepository) { this.repository = productRepository;
}
public ViewResult List(string category , int page = 1) {
ProductsListViewModel model = new ProductsListViewModel { Products = repository.Products
.Where(p => category == null || p.Category ==category)
.OrderBy(p => p.ProductID) Skip((page - 1) * PageSize) Take(PageSize),
PagingInfo = new PagingInfo {
CurrentPage = page, ItemsPerPage = PageSize, TotalItems = repository.Products.Count() },
CurrentCategory = category
};
return View(model);
} }
}
Trang 3Đã có 3 sự thay đổi đến phương thức hoạt động Đầu tiên, thông số
category đã được thêm vào Thông số category được sử dụng như là sự thay đổi thứ hai trong Listing, là thông số được nâng cấp đến truy vấn LINQ Nếu mà Category không có giá trị null, thì chỉ có đối tượng Product khớp với categoryđã chọn Thay đổi cuối cùng là đặt lại giá trị của tham số
CurrentCategory đã được thêm vào class ProductListViewModel.cs, Tuy nhiên, những thay đổi này sẽ làm tính toán giá trị của
PagInfo.TotalItems sai lệch
UNIT TEST: UPDATING EXISTING UNIT TEST
Thay đổi các tín hiệu của phương thức hoạt động của List, mà có thể ngăn chặn một số phương thức hiện hữu của unit test từ biên dịch.Để thực hiện, cần phải đặt Null như là tham số đầu tiên của phương thức List trong unit test mà đơn vị đó sẽ làm việc với controllers Ví dụ, trong kiểm tra
Can_Paginate, section hoat động của unit test trở thành:
[TestMethod]
public void Can_Send_Pagination_View_Model() {
// Arrange
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"},
new Product {ProductID = 4, Name = "P4"},
new Product {ProductID = 5, Name = "P5"}
Trang 4Những ảnh hưởng của việc lọc danh mục là hiển nhiên, dù cho đó là một thay đổi nhỏ Khởi động chương trình và chọn lựa danh mục dựa vào những chuỗi truy vấn, thay đổi port để chúng có thể tương thích với dự án trên visual của bạn:
Trang 5UNIT TEST: CATEGORY FILTERING
Cần có một unit test có thể kiểm tra đúng chức năng lọc danh sách,
để chắc chắn rằng bộ lộc có thể lọc chính xác bất cứ sản phẩm trong bất kì danh mục nào :
[TestMethod]
public void Can_Filter_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", Category = "Cat1"}, new Product {ProductID = 2, Name = "P2", Category = "Cat2"}, new Product {ProductID = 3, Name = "P3", Category = "Cat1"}, new Product {ProductID = 4, Name = "P4", Category = "Cat2"}, new Product {ProductID = 5, Name = "P5", Category = "Cat3"}
});
// Arrange - create a controller and make the page size 3 items
ProductController controller = new ProductController(mock.Object);
Assert.IsTrue(result[0].Name == "P2" && result[0].Category =="Cat2");
Assert.IsTrue(result[1].Name == "P4" && result[1].Category =="Cat2");
}
Việc test này tạo ra một kho lưu trữ mô hình các đối tượng Product
mà nó thuộc về một loạt các danh mục Khi một danh mục cụ thể được yêu cầu sử dụng dung phương thức Action, và kết quả được kiểm tra để chắc chắn rằng đó là kết quả đúng với đối tượng sản phẩm đã đươc yêu cầu
Refining the URL Scheme
Không một người dùng nào muốn thấy hay sử dụng một URL xấu như là /?categiry=Soccer.Để thay đổi, cần xem lại hướng định tuyến để tạo ra một cách tiếp cận khác tốt hơn cho người quản trị cũng như khách hàng đến URL Để thực hiện đổi mới đề án, cần thay đổi phương thức của
RegisterRoutes trong file App_Start/RouteConfig.cs
Listing 8-3: làm mới URL Scheme trong file RouteConfig.cs
Trang 6public class RouteConfig {
public static void RegisterRoutes(RouteCollection routes) {
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(null,"", new {
controller = "Product", action = "List", category = (string)null, page = 1
} );
routes.MapRoute(null,"Page{page}", new { controller = "Product", action = "List", category
=(string)null },
new { page = @"\d+" } );
routes.MapRoute(null,"{category}", new { controller = "Product", action = "List", page = 1 } );
routes.MapRoute(null,"{category}/Page{page}", new { controller = "Product", action = "List" }, new { page = @"\d+" }
);
routes.MapRoute(null, "{controller}/{action}");
} }
}
Lưu ý : việc thêm vào một định tuyến mới ở Listing 8-3 cho ta thấy là rất quan trọng Định tuyến được áp dụng trong thứ mà chúng được định nghĩa, và bạn sẽ thấy được những sự thay đổi khi bạn đổi thứ tự
Table 8-1 mô tả những những định tuyến đại diện cho URL Scheme
Table 8-1 : Route Summary
/Page 2 Các trang chỉ định khác, thể hiện các sản phẩm trong các
danh mục Soccer Thể hiện trang đầu tiên của các mục chỉ định
/Soccer/Page 2 Thể hiện các trang chỉ định của sản phẩm trong một danh
Trang 7Phương thức Url.Action là phương thức thuận tiện nhất để tạo những liên kết đi Ở chương trước, tác giả đã sử dụng cách thức hữu dụng này trong phương thức List để xem các hiển thị trong trang liên kết Tác giả
đã thêm những trợ giúp cho việc lọc danh mục, tác giả cần phải quay lại và đặt thông tin cho phương thức giúp đỡ này
Listing 8-4 :Thêm vào thông tin danh mục cho Pagination Link trong file List.cshtml
http:// <myserver> : <port> /Chess/Page1
Khi người dùng chọn vào những link kiểu như trên, thì danh mục hiện tại sẽ chuyển tới hoạt động của phương thức List, và bộ lọc sẽ đươc bảo
Trang 8quản Sau bạn đã thực hiện những bước thay đổi này, bạn có thể vào các URL như /Chess or /Soccer, và bạn có thể thấy chính xác liên kết trang ở dưới cùng của trang cũng như danh mục sản phẩm
Xây dựng danh mục điều hướng thể loại
Cần cung cấp cho người dùng một hướng để chọn danh mục mà không cần phải nhập liệu vào URLs Điều này có nghĩa là thể hiện chúng với các danh sách của danh mục tồn tại và được chỉ định, nếu có, đang được chọn Như việc xây dựng chương trình, tác giả sử dụng danh sách các danh mục cho nhiều hơn một controller, nên cần có gì đó có tính khép kín và có thể tái sử dụng
ASP.NET MVC Framework có một khái niệm là Child action, khái niệm
này thì tuyệt vời khi tạo một sản phẩm dựa trên việc tái sử dụng việc điều khiển điều hướng Child action dựa trên các phương pháp trợ giúp HTML được gọi là Html.Action,phương thức cho phép bạn bao gồm các đầu ra từ một phương pháp hành động tuỳ tiện nào trong giao diện hiện tại Trong trường hợp này, có thể tạo một điều khiển mới ( có thể gọi là
NavController) với phương thức hoạt động ( được gọi là Menu) hướng tới một menu điều hướng Html.Action sẽ được sử dụng để đưa output từ phương thức vào layout
Các tiếp cận này giúp mang lại một bộ điều khiển thực sự mà có thề chứa bất cứ ứng dụng logic đươc sử dụng và có thể là unit test như những điều khiển khác Đây là một hướng tốt để tạo ra các segment nhỏ hơn của ứng dụng trong khi vẫn bảo quản được cách tiếp cận MVC tổng thể
Tạo một điều khiển điều hướng
Nhấn chuột phải vào thư mục Controller trong đề án
Sportstore.WebUI và chọn Add->Controller từ pop-up trong menu Chọn thẻ MVC 5 Controller – Empty, sau đó chọn Add, đặt tên cho controller là
NavController và chọn Add để tạo lớp NavController.cs Xoá bỏ phương thức Index để cho Visual Studio thêm vào một mặc định điều khiển mới và phương thức hoạt động của Menu
Listing 8-5: Thêm menu phương thức hoạt động vào NavController.cs
using System.Web.Mvc;
namespace SportsStore.WebUI.Controllers {
public class NavController : Controller {
Trang 9public string Menu() {
return "Hello from NavController";
}
}
}
Phương thức trả về một chuỗi thông báo tĩnh nhưng bấy nhiêu là đủ
để bắt đầu trong khi tích hợp các Child action còn lại của chương trình Tác giả muốn danh sách các thể loại xuất hiện tất cả trên một trang, vì vậy tác giả trả lại các Child action vào layout, hơn là có một cái nhìn cụ thể Điều chỉnh View/Shared/_Layout.cshtml để có thể gọi phương thức trợ giúp
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="∼/Content/bootstrap.css" rel="stylesheet" />
<link href="∼/Content/bootstrap-theme.css" rel="stylesheet" />
<title>@ViewBag.Title</title>
</head>
<body>
<div class="navbar navbar-inverse" role="navigation">
<a class="navbar-brand" href="#">SPORTS STORE</a>
</div>
<div class="row panel">
<div id="categories" class="col-xs-3">
Trang 10Figure 8-2:
Khởi tạo danh sách thể loại
Bây giờ tác giả có thể trả về giá trị cho controller Nav và khởi tạo một giá trị thể loại thật.Tác giả không muốn khởi tạo URLs của thể loại trong controller.Thay vào đó, tác giả sẽ sữ dụng phương thức hỗ trợ trong view
để thực hiện Mọi thứ cần làm trong phương thức hoạt đông Menu là tạo danh sách thể loại, đã đươc thực hiện trong Listing 8-7
Listing 8-7:Triển khai thực hiện các Menu Method trong NavController.cs
public class NavController : Controller {
private IProductRepository repository;
public NavController(IProductRepository repo) {
repository = repo;
} public PartialViewResult Menu() {
IEnumerable<string> categories = repository.Products
.Select(x => x.Category) Distinct()
.OrderBy(x => x);
return PartialView(categories);
Trang 11} }
}
Thay đổi đầu tiên là thêm vào cấu trúc mà nó cho phép triển khai
IProductRepository như một đối số Điều này ảnh hưởng tới sự phụ thuộc
mà Ninject sẽ giải đáp khi nó được tạo ra một trường của class
NavController. Thay đổi thứ 2 là về hoạt động phương thứcMenu, mà nó bây giờ sử dụng vấn đáp LINQ để đạt được danh sách các thể loại từ trong kho và chuyển chúng tới view Chú ý, từ lúc tác giả làm việc với phần view trong controller này, tác giả đã gọi phương thức PartialView trong những phương thức hoạt động và nó là kết quả của đối tượng PartialViewResult.
UNIT TEST: GENERATING THE CATEGORYLIST
Các unit test cho thấy khả năng tạo ra những danh sách thể loại tương đối đơn giản Thành công của tác giả là tạo được một danh sách được sắp xếp theo bảng chữ cái và không có sự trùng lặp Cách đơn giản nhất để làm điều này là cung cấp các dữ liệu thử nghiệm mà nó có sự trùng lặp về thể loại và trong tình trạng không đươ đặt, chuyển đến NavController, và xác nhận những dữ liệu đã đươc hoàn toàn được chuyển đi:
[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"}, });
// 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");
Trang 12Khởi tạo View
Để tạo view cho phương thức hoạt động Menu, nhất chuột phải chọn mục View/Nav và chọn Add->MVC 5 View Page (Razor) từ cửa sổ chọn Đặt tên là Menuvà nhấn OK để tạo Menu.cshtml Loại bỏ những contents
mà Visual thêm vào cho view mới và đặt lại các content cho khớp với Listing 8-8
Listing 8-8:Những Content của file Menu.cshtml
@model IEnumerable<string>
@Html.ActionLink("Home", "List", "Product", null,
new { @class = "btn btn-block btn-default btn-lg" })
@foreach (var link in Model) {
@Html.RouteLink(link, new {
controller = "Product", action = "List",
category = link, page = 1
ActionLink để thực hiện điều này, phương thức này sẽ tạo ra một phần tử neo HTML sử dụng các thông tin định tuyết đươc cấu hình trước đó
Sau đó tác giả liệt kê những tên thể loại và tạo ra đường dẫn đến từng tên thể loại bằng cách sử dụng phương thức RouteLink. Nó gần giống như
ActionLink, nhưng nó cho phép tác giả cung cấp thiết lập về tên/ các cặp giá trị mà nó được đưa vào account khi khởi tạo một URL từ thiết lặp định tuyết Không cần phải lo lắng nếu chưa hình dung ra được định tuyết này Mọi thứ được tác giả giải thích trong chương 15
Trang 13Đường dẫn mà tác giả tạo thì sẽ trông không đẹp nhưng là cơ bản nhất, nên tác giả đã cung cấp thêm đối tượng cho cả hai phương thức hỗ trợ
ActionLink và RouteLinkmà nó xác định các giá trị cho các thuộc tính đã đươc tạo ra Đối tượng tác giả tạo để xác định giá trị của Class (tái sửa chữa với @ bởi vì Class dành riêng cho C# keyword) và thêm vào class Bootstrap để tạo kiểu cho các nút button lớn
Bạn có thể thấy đường dẫn vào thể loại nếu bạn chạy ứng dụng, được thể hiện ở Figure 8-3 Nếu bạn chọn vào category, thì danh sách những sản phẩm đã được cập nhật sẽ chỉ thể hiện sản phẩm từ category đã chọn
Figure 8-3:
Highlight danh mục hiện tại
Trang 14Tại thời điểm hiện tại, tác giả chưa chỉ ra cho user category mà họ đang xem.Nó có thể là bất cứ gì làm cho người customer suy luận từ những sản phẩm trong danh sách, nhưng tác giả mong muốn cung cấp những phản hồi thị giác Tác giả có thể làm điều này bằng việc tạo một view model mà
nó thuộc về danh sách các thể loại và sự chọn lựa các thể loại, và thực tế là,
đó là những gì mà tác giả thường làm Nhưng đối với phần nhiều tác giả đã
sử dụng tính năng view bag được giới thiệu ở chương 2 Chức năng này cho phép tác giả chuyển dữ liệu từ controller tới view mà không cần dùng đến view model Listing 8-9 sẽ thể hiện những thay đổi của phương thức hoạt động Menu trong controllerNav
Listing 8-9: Sử dụng View Bag trong NavController.cs
public class NavController : Controller {
private IProductRepository repository;
public NavController(IProductRepository repo) {
.OrderBy(x => x);
return PartialView(categories);
} }
}
Tác giả đã thêm vào thông số cho phương thức hoạt động Menu Giá trị cho thông số này sẽ được cung cấp một cách tự động bằng thông tin định tuyến Bên trong phần than của phương thức, tác giả đả gán động các tính chất của SelectedCategorytới đối tượng ViewBagvà đặt cho giá trị của nó
là current category Như tác giả đã giải thích ở chương 2, ViewBag là một đối tượng động và tác giả tạo một tính chất đơn giản dựa trên việc đặt giá trị cho chúng
UNIT TEST: REPORTING THE SELECTED CATEGORY
Trang 15Ở đây có thể kiểm tra phương thức hoạt động Menu một cách
chính xác cho biết thêm về giá trị của thể loại đươc chọn bằng việc đọc giá trị trong những tính chất của ViewBag trong các đơn vị kiểm tra, là thứ mà sẽ hiện hữu thông qua class ViewRuslt Đây là bài test:
…
[TestMethod]
public void Indicates_Selected_Category() {
// 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 = 4, Name = "P2", Category = "Oranges"},
});
// Arrange - create the controller
NavController target = new NavController(mock.Object);
// Arrange - define the category to selected
string categoryToSelect = "Apples";
Listing8-10: Làm nổi bật Selected Category trong file Menu.cshtml:
@model IEnumerable<string>
@Html.ActionLink("Home", "List", "Product", null,
new { @class = "btn btn-block btn-default btn-lg" })
@foreach (var link in Model) {