Please enter your details, and we'll ship your goods right away!
@using (Html.BeginForm()) { Ship to Name @Html.TextBoxFor(x => x.Name, new {@class = "form-control"}) Address foreach (var property in ViewData.ModelMetadata.Properties) { if (property.PropertyName != "Name" && property.PropertyName != "GiftWrap") { @(property.DisplayName ?? property.PropertyName) @Html.TextBox(property.PropertyName, null, new {@class = "form-control"}) } } Options @Html.EditorFor(x => x.GiftWrap) Gift wrap these items } Thuộc tính static ViewData.ModelMetadata trả đối tượng System.Web.MVC.ModelMetaData cung cấp thông tin loại mơ hình cho giao diện hiển thị Thuộc tính Properties tơi sử dụng vòng lặp foreach trả tập hợp đối tượng ModelMetaData, đối tượng số đại diện cho thuộc tính xác định loại mơ hình Tơi sử dụng property PropertyName để đảm bảo không tạo nội dung cho properties Name GiftWrap tạo tập hợp thành phần, hoàn chỉnh với lớp Bootstrap, cho tất properties khác Tip: Từ khố for if mà tơi sử dụng nằm phạm vi biểu thức Razor (biểu thức @using tạo form), tơi không cần phải thêm tiền tố với ký tự @ Trong thực tế, làm vậy, Razor báo lỗi Nó chút thời gian để có sử dụng để ký tự @ cần thiết với Razor, trở thành chất thứ hai hầu hết lập trình viên Đối với người khơng thể hồn tồn nhận lần (trong bao gồm tôi), thông báo lỗi Razor hiển thị trình duyệt cung cấp hướng dẫn cụ thể để sửa chữa sai lầm Tôi không thực hồn tồn được, nhiên Nếu bạn chạy ví dụ nhìn vào đầu tạo giao diện, bạn thấy số label khơng hồn tồn xác, Figure 9-5 minh họa Figure 9-5 The problem with generating labels from property names Kiểm tra xem có giá trị DisplayName có sẵn tạo thành phần form, này: @( property.DisplayName ?? property.PropertyName) Để tận dụng lợi property DisplayName, tơi cần phải áp dụng thuộc tính Display với lớp mơ hình, Listing 9-13 Listing 9-13 Áp dụng thuộc tính Display với tập tin ShippingDetails.cs 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" ) ] [Display(Name="Line 1" ) ] public string Line1 { get; set; } [Display(Name = "Line 2" ) ] public string Line2 { get; set; } [Display(Name = "Line 3" ) ] 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; } } } Thiết lập giá trị Name cho thuộc tính Display cho phép để thiết lập giá trị đọc property DisplayName giao diện Bạn thấy hiệu ứng cách khởi động ứng dụng xem trang toán, thể Figure 9-6 Figure 9-6 The effect ofthe Display attribute on the model type Ví dụ cho thấy hai khía cạnh khác làm việc với MVC Framework Việc bạn làm việc xung quanh tình trạng để đơn giản hóa việc đánh dấu viết mã bạn Thứ hai giao diện chạy mơ hình MVC hạn chế việc hiển thị liệu đánh dấu, công cụ mà Razor C# cung cấp cho nhiệm vụ phong phú linh hoạt, chí đến mức làm việc với loại siêu liệu Implementing the Order Processor Tôi cần thành phần ứng dụng để chi tiết đơn đặt hàng để xử lý Để phù hợp với ngun tắc mơ hình MVC, định nghĩa interface cho chức này, viết thực thi interface, sau kết hợp hai container DI, Ninject Defining the Interface Thêm interface gọi IOrderProcessor vào thư mục Abstract dự án SportsStore.Domain chỉnh sửa nội dung cho khớp Listing 9-14 Listing 9-14 The Contents of the IOrderProcessor.cs File using SportsStore.Domain.Entities; namespace SportsStore.Domain.Abstract { public interface IOrderProcessor { void ProcessOrder( Cart cart, ShippingDetails shippingDetails) ; } } Implementing the Interface Việc thực thi IOrderProcessor xử lý đơn đặt hàng cách gửi chúng vào trang quản trị Tơi đơn giản hóa q trình bán hàng, tất nhiên Hầu hết trang web thương mại điện tử không đơn đặt hàng e-mail đơn giản, không cung cấp hỗ trợ cho xử lý thẻ tín dụng hình thức tốn khác Nhưng tơi muốn giữ cho thứ tập trung vào MVC, sử dụng e-mail Tạo tập tin lớp gọi EmailOrderProcessor.cs thư mục bê tông dự án SportsStore.Domain chỉnh sửa nội dung cho khớp Listing 9-15 Lớp sử dụng xây dựng SMTP hỗ trợ bao gồm thư viện NET Framework để gửi e-mail Listing 9-15 The Contents of the EmailOrderProcessor.cs File using System.Net; using System.Net.Mail; using System.Text; using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; namespace SportsStore.Domain.Concrete { public class EmailSettings { public string MailToAddress = "orders@example.com" ; public string MailFromAddress = "sportsstore@example.com" ; public bool UseSsl = true; public string Username = "MySmtpUsername" ; public string Password = "MySmtpPassword" ; public string ServerName = "smtp.example.com"; public int ServerPort = 587; public bool WriteAsFile = false; public string FileLocation = @"c:\sports_store_emails" ; } public class EmailOrderProcessor : IOrderProcessor { private EmailSettings emailSettings; public EmailOrderProcessor( EmailSettings settings) { emailSettings = settings; } public void ProcessOrder( Cart cart, ShippingDetails shippingInfo) { using ( var smtpClient = new SmtpClient( ) ) { smtpClient.EnableSsl = emailSettings.UseSsl; smtpClient.Host = emailSettings.ServerName; smtpClient.Port = emailSettings.ServerPort; smtpClient.UseDefaultCredentials = false; smtpClient.Credentials = new NetworkCredential( emailSettings Username, emailSettings.Password) ; if ( emailSettings.WriteAsFile) { smtpClient.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory; smtpClient.PickupDirectoryLocation = emailSettings.FileLocation; smtpClient.EnableSsl = false; } StringBuilder body = new StringBuilder( ) AppendLine( " A new order has been submitted" ) AppendLine( " -" ) AppendLine( " Items: " ) ; foreach ( var line in cart.Lines) { var subtotal = line.Product.Price * line.Quantity; body.AppendFormat( " { 0} x { 1} ( subtotal: { 2: c} " , line.Quantity, line.Product.Name, subtotal) ; } body.AppendFormat( " Total order value: { 0: c} " , cart.ComputeTotalValue( ) ) AppendLine( " -" ) AppendLine( " Ship to: " ) AppendLine( shippingInfo.Name) AppendLine( shippingInfo.Line1) AppendLine( shippingInfo.Line2 ?? "" ) AppendLine( shippingInfo.Line3 ?? "" ) AppendLine( shippingInfo.City) AppendLine( shippingInfo.State ?? "" ) AppendLine( shippingInfo.Country) AppendLine( shippingInfo.Zip) AppendLine( " -" ) AppendFormat( "Gift wrap: { 0} " , shippingInfo.GiftWrap ? "Yes" : "No" ) ; MailMessage mailMessage = new MailMessage( emailSettings MailFromAddress, //From emailSettings MailToAddress, //To " New order submitted! ", //Subject body.ToString( ) ) ; //Body if ( emailSettings.WriteAsFile) { mailMessage.BodyEncoding = Encoding.ASCII; } smtpClient.Send(mailMessage) ; } } } } Để đơn giản, định nghĩa lớp EmailSettings Listing 9-15 Một instance lớp yêu cầu cấu trúc EmailOrderProcessor chứa tất cài đặt cần thiết để cấu hình lớp NET e-mail Tip: Đừng lo lắng bạn khơng có máy chủ SMTP có sẵn Nếu bạn thiết lập property EmailSettings.WriteAsFile true, thông điệp e-mail viết tập tin vào thư mục định property FileLocation Thư mục phải tồn có khả ghi Các tập tin viết với mở rộng eml, chúng đọc với trình soạn thảo văn Vị trí tơi thiết lập listing c:\sports_store_emails Registering the Implementation Bây tơi có thực thi interface IOrderProcessor phương tiện để cấu hình nó, tơi sử dụng Ninject để tạo instance Chỉnh sửa tập tin NinjectDependencyResolver.cs thư mục Infrastructure project SportsStore.WebUI thực thay đổi thể Listing 9-16 với phương thức AddBindings Listing 9-16 Adding Ninject Bindings for IOrderProcessor to the NinjectDependencyResolver.cs File using System; using System.Collections.Generic; using System.Configuration; using System.Web.Mvc; using Moq; using Ninject; using SportsStore.Domain.Abstract; using SportsStore.Domain.Concrete; using SportsStore.Domain.Entities; namespace SportsStore.WebUI.Infrastructure { public class NinjectDependencyResolver : IDependencyResolver { private IKernel kernel; public NinjectDependencyResolver( IKernel kernelParam) { kernel = kernelParam; AddBindings( ) ; } public object GetService( Type serviceType) { return kernel.TryGet( serviceType) ; } public IEnumerable GetServices( Type serviceType) { return kernel.GetAll( serviceType) ; } private void AddBindings( ) { kernel.Bind( ).To( ) ; EmailSettings emailSettings = new EmailSettings { WriteAsFile = bool.Parse(ConfigurationManager AppSettings["Email WriteAsFile"] ?? " false" ) }; kernel.Bind().To() WithConstructorArgument("settings" , emailSettings) ; } } } Tôi tạo đối tượng EmailSettings, mà sử dụng với phương pháp Ninject WithConstructorArgument để tơi thêm vào constructor EmailOrderProcessor trường hợp tạo để yêu cầu dịch vụ cho giao diện IOrderProcessor Trong Liệt kê 9-16, định giá trị để thuộc tính EmailSettings: WriteAsFile Tơi đọc giá trị tài sản cách sử dụng ConfigurationManager Tài sản AppSettings, cung cấp truy cập để cài đặt ứng dụng định nghĩa Web.config (các file thư mục dự án root), thể Liệt kê 9-17 Bảng liệt kê 9-17 Cài đặt ứng dụng tập tin Web.config Hoàn thành Cart Controller Để hoàn thành lớp CartController, cần chỉnh sửa kiến trúc để yêu cầu thực giao diện IOrderProcessor thêm phương thức hành động mà xử lý yêu cầu POST biểu mẫu HTTP người dùng nhấp chuột vào nút Complete order Listing 918 cho thấy hai thay đổi Listing 9-18 Hoàn thành việc điều khiển tập tin CartController.cs using System.Linq; using System.Web.Mvc; using SportsStore.Domain.Abstract; using SportsStore.Domain.Entities; using SportsStore.WebUI.Models; namespace SportsStore.WebUI.Controllers { public class CartController : Controller { private IProductRepository repository; private IOrderProcessor orderProcessor; public CartController(IProductRepository repo, IorderProcessor proc) { repository = repo; orderProcessor = proc; } // phương thức action bỏ qua cho ngắn gọn public ViewResult Checkout() { return View(new ShippingDetails()); } [HttpPost] public ViewResult Checkout(Cart cart, ShippingDetails shippingDetails) { if (cart.Lines.Count() == 0) { ModelState.AddModelError("", "Sorry, your cart is empty!") ; } if (ModelState.IsValid) { orderProcessor.ProcessOrder(cart, shippingDetails); cart.Clear(); return View("Completed"); } else { return View(shippingDetails); } } } } Bạn thấy phương thức Checkout tơi thêm gắn với thuộc tính HttpPost, có nghĩa gọi cho yêu cầu POST - trường hợp này, người dùng submit form Một lần nữa, tơi dựa vào hệ thống nối kết mơ hình, cho tham số ShippingDetails (được tạo tự động cách sử dụng liệu form HTTP) tham số Cart (được tạo cách sử dụng kết nối tùy chỉnh) Chú ý: Sự thay đổi kiến trúc bắt buộc cập nhật kiểm thử tạo cho lớp CartController Gán null cho tham số kiến trúc cho phép biên dịch unit test Các MVC Framework kiểm tra ràng buộc validation mà áp dụng để ShippingDetails sử dụng thuộc tính thích liệu, vi phạm vấn đề validation chuyền sang phương thức action thơng qua thuộc tính ModelState Tơi thấy có vấn đề cách kiểm tra thuộc tính ModelState.IsValid Chú ý gọi phương thức ModelState.AddModelError để ghi thông báo lỗi khơng có sản phẩm giỏ hàng Tơi giải thích làm để hiển thị lỗi thời gian ngắn, tơi có nhiều để nói mơ hình liên kết - model binding validation Chương 24 25 UNIT TEST: ORDER PROCESSING Để hoàn tất việc unit test cho lớp CartController, cần phải kiểm thử cách hoạt động phiên phương thức Checkout Tôi muốn xử lý đơn hàng có giỏ hàng khách hàng cung cấp chi tiết giao hàng hợp lệ Trong trường hợp khác, khách hàng hiển thị lỗi Đây phương thứ kiểm thử đầu tiên: [TestMethod] public void Cannot_Checkout_Empty_Cart() { // Chuẩn bị – tạo đơn hàng giả Mock mock = new Mock(); // Chuẩn bị – tạo giỏ hàng trống Cart cart = new Cart(); // Chuẩn bị – tạo chi tiết vận chuyển ShippingDetails shippingDetails = new ShippingDetails(); // Chuẩn bị – tạo controller CartController target = new CartController(null, mock.Object); // Thực ViewResult result = target.Checkout(cart, shippingDetails); // Xác nhận – kiểm tra đơn hàng không thông qua vào xử lý mock.Verify(m => m.ProcessOrder(It.IsAny(), It.IsAny()), Times.Never()); // Xác nhận – kiểm tra xem phương thức trở trang mặc định Assert.AreEqual("", result.ViewName); // Xác nhận - kiểm tra tơi chuyển mơ hình hợp lệ sang giao diện hiển thị Assert.AreEqual(false, result.ViewData.ModelState.IsValid); } Kiểm tra đảm bảo tốn với giỏ rỗng Tơi kiểm tra điều cách đảm bảo ProcessOrder việc thực IOrderProcessor giả không gọi, trang mà phương thức trả trang mặc định (trong hiển thị lại liệu nhập vào khách hàng cung cấp cho họ hội để sửa nó), model state chuyển tới trang đánh dấu không hợp lệ Phương pháp kiểm thử hoạt động theo cách tương tự, thêm lỗi vào mơ hình view để mô vấn đề cách kết nối mô hình (điều xảy khách hàng nhập liệu chuyển hàng không hợp lệ): [TestMethod] public void Cannot_Checkout_Invalid_ShippingDetails( ) { // Sắp xếp – tạo xử lý đơn hàng giả Mock mock = new Mock(); // Chuẩn bị – tạo giỏ hàng với hàng Cart cart = new Cart( ); cart.AddItem(new Product( ), 1); // Chuẩn bị - tạo controller CartController target = new CartController(null, mock.Object); // Chuẩn bị - thêm lỗi vào mơ hình target.ModelState.AddModelError("error", "error"); // Thực – thử toán ViewResult result = target.Checkout(cart, new ShippingDetails()); // Xác nhận – kiểm tra xem đơn hàng không chuyển sang xử lý mock.Verify(m => m.ProcessOrder(It.IsAny(), It.IsAny()), Times.Never()); // Xác nhận – kiểm tra xem phương thức trả trang mặc định Assert.AreEqual("", result.ViewName); // Xác nhận – kiểm tra xem chuyển mô hình khơng hợp lệ sang giao diện hiển Assert.AreEqual(false, result.ViewData.ModelState.IsValid); } Đã xác định giỏ rỗng chi tiết không hợp lệ ngăn chặn đơn đặt hàng xử lý, cần phải đảm bảo xử lý đơn đặt hàng thích hợp Dưới kiểm tra: [TestMethod] public void Can_Checkout_And_Submit_Order( ) { // Arrange - create a mock order processor Mock mock = new Mock(); // Arrange - create a cart with an item Cart cart = new Cart( ); cart.AddItem( new Product( ), 1); // Arrange - create an instance of the controller CartController target = new CartController( null, mock.Object); // Act - try to checkout ViewResult result = target.Checkout(cart, new ShippingDetails( )); // Assert - check that the order has been passed on to the processor mock.Verify( m => m.ProcessOrder( It.IsAny( ), It.IsAny( )), Times.Once( )); // Assert - check that the method is returning the Completed view Assert.AreEqual( " Completed" , result.ViewName); // Assert - check that I am passing a valid model to the view Assert.AreEqual(true, result.ViewData.ModelState.IsValid); } Chú ý khơng cần phải thử nghiệm mà tơi xác định chi tiết giao hàng hợp lệ Điều xử lý hồn tồn tự động mơ hình binder cách sử dụng thuộc tính áp dụng cho thuộc tính lớp ShippingDetails Displaying Validation Errors MVC Framework sử dụng thuộc tính validation áp dụng cho lớp ShippingDetails để xác nhận liệu người dùng Tuy nhiên, cần phải thực vài thay đổi để hiển thị vấn đề cho người dùng Vấn đề cần phải cung cấp tóm tắt vấn đề cho người dùng Điều đặc biệt quan trọng đối phó với vấn đề khơng liên quan đến lĩnh vực cụ thể, chẳng hạn người dùng cố gắng để kiểm tra khơng có sản phẩm giỏ hàng Để hiển thị tóm tắt hữu ích lỗi validation, tơi sử dụng phương thức trợ giúp Html.ValidationSummary, giống làm Chapter Listing 919 cho thấy việc bổ sung vào Checkout.cshtml Listing 9-19 Thêm Validation Summary vào tập tin Checkout.cshtml @using (Html.BeginForm( )) { @Html.ValidationSummary() Ship to Name @Html.TextBoxFor( x => x.Name, new { @class = "form-control" } ) Address Bước tạo số CSS style nhắm đến lớp sử dụng tóm tắt validation mà MVC Framework thêm phần tử không hợp lệ Tôi tạo Style Sheet gọi ErrorStyles.css thư mục Content project SportsStore.WebUI xác định style hiển thị Listing 9-20 Đây tập hợp style mà sử dụng Chapter Listing 9-20 Nội dung tập tin ErrorStyles.css field-validation-error { color: #f00; } field-validation-valid { display: none; } input-validation-error { border: 1px solid #f00; backgroundcolor: #fee; } validation-summary-errors { font-weight: bold; color: #f00;} validation-summary-valid { display: none; } Trong đơn hàng, để sử dụng style, cập nhật tập tin _Layout.cshtml để thêm phần tử liên kết với tập tin ErrorStyles.css, thể Listing 9-21 Listing 9-21 Thêm phần tử liên kết tập tin _Layout.cshtml @ViewBag.Title