1. Trang chủ
  2. » Công Nghệ Thông Tin

9 chuong 09

30 34 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 30
Dung lượng 1,04 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Sử dụng Model Binding Các MVC Framework sử dụng một hệ thống gọi là model binding – mô hình ràng buộc để tạo ra đối tượng C # từ yêu cầu HTTP để chuyển chúng như các giá trị tham số cho

Trang 1

CHAPTER 9

SportsStore: Completing the Cart

Trong chương này, tiếp tục xây dựng các SportsStore tương tự app Trong chương trước, Chúng ta thêm hổ trợ căn bản cho việc mua cart và bây giờ chúng ta sẽ cải thiện và hoàn thành chức năng này

Sử dụng Model Binding

Các MVC Framework sử dụng một hệ thống gọi là model binding – mô hình ràng buộc để tạo

ra đối tượng C # từ yêu cầu HTTP để chuyển chúng như các giá trị tham số cho các phương thức hoạt động Đây là cách các framework MVC xử lý form, ví dụ: nhìn vào các thông số

của các phương thức hoạt động được đạt ra và sử dụng một model binder để có các giá trị form gửi bởi trình duyệt và chuyển đổi chúng sang các kiểu của tham số cùng dạng trước khi chuyển chúng đến các phương thức hoạt động

Mô hình binders có thể tạo ra C # chuẩn từ bất kỳ thông tin có sẵn trong yêu cầu Đây là một trong những đặc điểm chính của MVC Framework Tôi sẽ tạo ra một mô hình binder tùy chỉnh

để cải thiện class CartController

Tôi thích sử dụng các tính năng trong bộ điều khiển Cart để lưu trữ và quản lý các đối tượng Cart mà tôi thiết lập trong Chương 8, nhưng tôi không thích cách kiểm tra của nó Nó không phù hợp với phần còn lại của các mô hình ứng dụng, mà là dựa trên các thông số hoạt động

Có thể có các đơn vị sai trong class CartController muốn kiểm tra tôi phải thử các tham

số của lớp cơ sở

Để giải quyết vấn đề này, tôi sẽ tạo ra một mô hình Binder tùy chỉnh có thể lấy được các đối tượng Cart chứa trong data Trong MVC Framework sẽ tạo ra các đối tượng Cart và làm chúng như tham số cho các phương pháp hoạt động trong lớp CartController Các tính năng ràng buộc mô hình mạnh mẽ và linh hoạt Đi sâu hơn về tính năng này trong Chương

24

Creating a Custom Model Binder

Tôi tạo một mô hình Binder tuỳ chỉnh bằng triển khai interface System.Web.Mvc.IModelBinder Để thực hiện, thêm một thư mục vào project SportsStore.WebUI được gọi là Infrastructure/Binders và tạo một tập tin lớp CartModelBinder.cs bên trong đó Listing 9-1 cho thấy các nội dung của tập tin mới

Trang 2

Listing 9-1 The Contents of the CartModelBinder.cs File

using System.Web.Mvc;

using SportsStore.Domain.Entities;

namespace SportsStore.WebUI.Infrastructure.Binders {

public class CartModelBinder : IModelBinder {

private const string sessionKey = "Cart";

public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {

// get the Cart from the session

Cart cart = null;

Đối với mục đích của tôi, lớp ControllerContext là thứ tôi đang quan tâm Nó

có tính năng HttpContext,mà lần lượt có một tính năng Session cho phép tôi có được và

thiết lập dữ liệu session Tôi có thể có được các đối tượng Cart liên kết với session người dùng bằng cách đọc một giá trị từ dữ liệu session, và tạo ra một Cart được nếu không có

Tôi cần phải nói cho MVCFramework rằng nó có thể sử dụng lớp CartModelBinder để tạo ra các trường của Cart Tôi làm điều này trong các phương pháp Application_Start của Global.asax, như thể hiện trong Listing 9-2

Listing 9-2 Registering the CartModelBinder Class in the Global.asax.cs File

using System;

using System.Collections.Generic;

Trang 3

public class MvcApplication : System.Web.HttpApplication {

protected void Application_Start() {

Bây giờ tôi có thể cập nhật các bộ điều khiển Cart để loại bỏ các phương pháp getCart

và dựa trên các mô hình Binder cung cấp bộ điều khiển với các đối tượng của Cart Listing 9-3 cho thấy các thay đổi

Listing 9-3 Relying on the Model Binder in the CartController.cs File

public class CartController : Controller {

private IProductRepository repository;

public CartController(IProductRepository repo) {

repository = repo;

}

public ViewResult Index(Cart cart, string returnUrl) {

return View(new CartIndexViewModel {

Trang 4

productId, string returnUrl) {

Product product = repository.Products.FirstOrDefault(p =>

Có rất nhiều lợi ích khi sử dụng Binder tùy chỉnh mô hình như thế này Việc đầu tiên

là tôi đã phân chia các logic được sử dụng để tạo ra một Cart từ bộ điều khiển, cho phép tôi thay đổi cách tôi lưu trữ đối tượng Cart mà không cần phải thay đổi bộ điều khiển Lợi ích thứ hai là bất kỳ class điều khiển mà làm việc với các đối tượng Cart chỉ có thể khai báo chúng như tham số phương thức hoạt động và tận dụng mô hình tùy chỉnh Binder Lợi ích thứ

ba quan trọng nhất, tôi bây giờ có thể kiểm tra đơn vị điều khiển Cart mà không cần phải thử rất nhiều ASP.NET plumbing

UNIT TEST: THE CART CONTROLLER

Tôi có thể kiểm tra đơn vị class CartController bằng cách tạo ra các đối tượng Cart

và chuyển chúng tới các phương pháp hoạt động Tôi muốn thử nghiệm ba khía cạnh khác nhau của bộ điều khiển này:

 Phương pháp AddToCart nên thêm tìm kiếm product trong tuỳ chĩnh Cart

 Sau khi thêm một product vào cart, người sử dụng nên được chuyển đến giao diện Index

 URL mà người dùng có thể trở về danh mục cần được thông qua một cách chính xác

để các phương thức hoạt động Index

Tôi thêm file CartTests.cs trong project portsStore.UnitTests:

Trang 5

public class CartTests {

// existing test methods omitted for brevity

[TestMethod]

public void Can_Add_To_Cart() {

// 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"},

}.AsQueryable());

// Arrange - create a Cart

Cart cart = new Cart();

// Arrange - create the controller

CartController target = new CartController(mock.Object);

// Act - add a product to the cart

target.AddToCart(cart, 1, null);

// Assert

Assert.AreEqual(cart.Lines.Count(), 1);

Assert.AreEqual(cart.Lines.ToArray()[0].Product.ProductID, 1); }

[TestMethod]

public void Adding_Product_To_Cart_Goes_To_Cart_Screen() {

// 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"},

}.AsQueryable());

// Arrange - create a Cart

Cart cart = new Cart();

// Arrange - create the controller

CartController target = new CartController(mock.Object);

// Act - add a product to the cart

Trang 6

RedirectToRouteResult result = target.AddToCart(cart, 2,

public void Can_View_Cart_Contents() {

// Arrange - create a Cart

Cart cart = new Cart();

// Arrange - create the controller

CartController target = new CartController(null);

// Act - call the Index action method

Tôi đã xác định được và thử nghiệm các phương thức hoạt động RemoveFromCart trong

bộ điều khiển, do đó cho phép các khách hàng loại bỏ mặt hàng này nhưng vấn đề phương pháp này trong một lần xem, mà tôi sẽ làm bằng cách thêm một nút Remove trong mỗi hàng của tóm tắt cart Những thay đổi để Views/Cart/Index.cshtml được hiển thị trong Listing 9-4

Listing 9-4 Introducing a Remove Button to the Index.cshtml File

@model SportsStore.WebUI.Models.CartIndexViewModel

@{

ViewBag.Title = "Sports Store: Your Cart";

}

Trang 7

<td>

@using (Html.BeginForm("RemoveFromCart", "Cart")) { @Html.Hidden("ProductId",line.Product.ProductID) @Html.HiddenFor(x => x.ReturnUrl)

<input class="btn btn-sm btn-warning" type="submit" value="Remove" />

Trang 8

Tôi đã thêm một cột mới cho mỗi hàng của bảng có chứa một form với phần tử Input Tôi

đã định dạng phần tử Input như là một nút với Bootstrap và thêm một phần tử style và một id vào phần tử table để đảm bảo rằng các nút và các nội dung của các cột khác được liên kết đúng

Lưu ý: Tôi dùng các phương thức trợ giúp Html.HiddenFor để tạo ra một field ẩn

cho thuộc tính mô hình ReturnUrl, nhưng tôi đã phải sử dụng phương thức trợ giúp Html.Hidden kiểu chuỗi làm tương tự cho các field ProductID Nếu tôi đã viết Html.HiddenFor(x => line.Product.ProductID), các helper tạo field ẩn với tên line.Product.ProductID Tên của trường này sẽ không phù hợp với tên của các tham số cho phương thức hoạt động CartController.RemoveFromCart, mà sẽ ngăn chặn các mô hình Binders mặc định từ công việc, do đó các MVC Framework sẽ không thể gọi phương thức

Bạn có thể thấy các nút Remove tại nơi làm việc bằng cách chạy các ứng dụng và thêm các mục vào giỏ mua hàng Hãy nhớ rằng các giỏ đã có chứa các chức năng để loại bỏ

nó, mà bạn có thể kiểm tra bằng cách nhấn vào một trong các nút mới, như thể hiện trong Figure 9-1

Trang 9

Figure 9-1 Xóa một món hàng từ giỏ hàng

Adding the Cart Summary

Tôi có thể có một chức năng cart, nhưng có một vấn đề với cách mà nó được tích hợp vào giao diện Khách hàng có thể cho biết những gì có trong cart của họ chỉ bằng cách xem màn hình cart summary Và họ có thể xem màn hình cart summary chỉ bằng cách thêm một mục mới vào cart

Để giải quyết vấn đề này, tôi sẽ thêm một tiện ích tóm tắt nội dung của cart và có thể bấm để hiển thị các nội dung cart thông qua ứng dụng Tôi sẽ làm điều này như cách mà tôi

đã thêm widget như chuyển hướng một hoạt động output tôi sẽ bố trí vào các Razor Để bắt đầu, tôi cần phải thêm các phương thức đơn giản, thể hiện trong Listing 9-5, đến class CartController

Listing 9-5 Adding the Summary Method to the CartController.cs File

public class CartController : Controller {

private IProductRepository repository;

Trang 10

public CartController(IProductRepository repo) {

repository = repo;

}

// other action methods omitted for brevity

public PartialViewResult Summary(Cart cart) {

Listing 9-6 The Contents of the Summary.cshtml File

@model SportsStore.Domain.Entities.Cart

<div class="navbar-right">

@Html.ActionLink("Checkout", "Index", "Cart",

new { returnUrl = Request.Url.PathAndQuery },

new { @class = "btn btn-default navbar-btn" })

Listing 9-7 Adding the Summary Partial View to the _Layout.cshtml File

<link href="∼/Content/bootstrap.css" rel="stylesheet" />

<link href="∼/Content/bootstrap-theme.css" rel="stylesheet" /> <title>@ViewBag.Title</title>

Trang 11

</head>

<body>

<div class="navbar navbar-inverse" role="navigation">

<a class="navbar-brand" href="#">SPORTS STORE</a>

@Html.Action("Summary", "Cart")

</div>

<div class="row panel">

<div id="categories" class="col-xs-3">

Figure 9-2 The cart summary widget

Với sự bổ sung này, khách hàng biết những gì có trong giỏ hàng của họ một cách rõ ràng, kiểm tra từ cửa hàng Bạn có thể thấy, một lần nữa sử dụng phương thức Html.Action giúp kết hợp các đầu ra từ một phương pháp hành động trong các view khác Đây là kỹ thuật tốt để phá bỏ các chức năng của một ứng dụng vào riêng biệt, dãy sử dụng lại

Submitting Orders

Bây giờ đến tính năng cuối cùng trong SportsStore: kiểm tra và hoàn thành một đơn đặt hàng Trong các phần sau, tôi sẽ mở rộng mô hình miền để cung cấp hỗ trợ chi tiết vận chuyển từ một người sử dụng và thêm hỗ trợ ứng dụng để xử lý các chi tiết đó

Trang 12

Extending the Domain Model

Thêm một tập tin class gọi là ShippingDetails.cs vào thư mục Entities của project SportsStore.Domain và chỉnh sửa nó cho phù hợp với nội dung được hiển thị trong Listing 9-8 Đây là lớp sẽ được dùng để đại diện cho các chi tiết giao hàng cho khách hàng

Listing 9-8 The Contents of the ShippingDetails.cs File

using System.ComponentModel.DataAnnotations;

namespace SportsStore.Domain.Entities {

public class ShippingDetails {

[Required(ErrorMessage = "Please enter a name")]

public string Name { get; set; }

[Required(ErrorMessage = "Please enter the first address line")]

public string Line1 { get; set; }

public string Line2 { get; set; }

public string Line3 { get; set; }

[Required(ErrorMessage = "Please enter a city name")]

public string City { get; set; }

[Required(ErrorMessage = "Please enter a state name")]

public string State { get; set; }

public string Zip { get; set; }

[Required(ErrorMessage = "Please enter a country name")] public string Country { get; set; }

public bool GiftWrap { get; set; }

}

}

Bạn có thể thấy tôi đang sử dụng các thuộc tính xác nhận từ namespace System.ComponentModel.DataAnnotations, giống như tôi đã làm trong Chapter

2 Tôi sẽ phân tích rõ hơn ở Chapter 25

Lưu ý: Lớp ShippingDetails không có bất kỳ chức năng, vì vậy không có thể kiểm

thử unit test

Adding the Checkout Process

Mục đích là để người dùng có thể nhập thông tin vận chuyển của họ và nộp đơn đặt hàng của

họ Tôi cần phải thêm một nút Checkout để xem tóm tắt giỏ hàng Listing 9-9 cho thấy sự thay đổi tôi áp dụng cho tập tin Views/Cart/Index.cshtml

Listing 9-9 Adding the Checkout Now Button to the Index.cshtml File

Trang 13

Figure 9-3 The Checkout now button

Như bạn mong đợi, bây giờ tôi cần định nghĩa phương thức Checkout trong lớp CartController, như Listing 9-10

Listing 9-10 The Checkout Action Method in the CartController.cs File

public class CartController : Controller {

private IProductRepository repository;

public CartController(IProductRepository repo) {

repository = repo;

}

// other action methods omitted for brevity

public ViewResult Checkout() {

Trang 14

return View(new ShippingDetails());

Listing 9-11 The Contents of the Checkout.cshtml File

@model SportsStore.Domain.Entities.ShippingDetails

@{

ViewBag.Title = "SportStore: Checkout";

}

<h2>Check out now</h2>

<p>Please enter your details, and we'll ship your goods right away!</p>

Trang 16

Figure 9-4 The shipping details form

Vấn đề với giao diện này là nó có chứa rất nhiều đánh dấu lặp đi lặp lại Sử dụng MVC Framework giúp HTML để có thể làm giảm sự trùng lặp, nhưng chúng khó cấu trúc và định dạng nội dung theo cách mà tôi muốn Thay vào đó, tôi sẽ sử dụng một tính năng lấy metadata

về các đối tượng mô hình giao diện và kết hợp nó với một kết hợp của C# và biểu thức Razor Bạn có thể thấy những gì tôi đã làm trong Listing 9-12

Trang 17

Listing 9-12 Reducing Duplication in the Checkout.cshtml File

@model SportsStore.Domain.Entities.ShippingDetails

@{

ViewBag.Title = "SportStore: Checkout";

}

<h2>Check out now</h2>

<p>Please enter your details, and we'll ship your goods right away!</p>

foreach (var property in ViewData.ModelMetadata.Properties) {

if (property.PropertyName != "Name" && property.PropertyName !=

Ngày đăng: 23/10/2019, 21:16

TỪ KHÓA LIÊN QUAN

w