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

6 chuong 06

34 35 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 34
Dung lượng 1,02 MB

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

Nội dung

Đưa các giá trị property và constructor vào các đối tượng mới được tạo ra Tự động chọn một lớp triển khai cho một giao diện Kiểm soát vòng đời của các đối tượng mà Ninject tạo ra

Trang 1

CHƯƠNG 6

Công cụ cần thiết cho MVC

Trong chương này, tôi sẽ xem xét ba công cụ mà mọi lập trình viên MVC đều cần phải có: một dependency injection (DI) container, một framework kiểm thử đơn vị, và một công cụ làm giả

Tôi đã chọn ba triển khai cụ thể của các công cụ trên cho cuốn sách này, nhưng cũng có rất nhiều lựa chọn thay thế khác cho từng loại Nếu bạn không quen thuộc với những ci mà tôi sử dụng, đừng nên lo lắng Chúng có rất nhiều, chắc chắn là bạn sẽ tìm thấy một cái gì đó phù hợp với mình

Như đã đề cập ở Chương 3, Ninject là DI container ưa thích của tôi Nó đơn giản, thanh lịch và dễ sử dụng Có nhiều lựa chọn tinh tế hơn, nhưng tôi thích cách mà Ninject làm việc mà không phải cấu hình quá nhiều Nếu bạn không thích Ninject, tôi khuyên bạn nên thử Unity, nó là một sự thay thế từ Microsoft

Đối với kiểm thử đơn vị, tôi sẽ sử dụng các công cụ kiểm thử được xây dựng sẵn trong Visual Studio Tôi từng sử dụng NUnit,

nó mà là một framework kiểm thử đơn vị NET phổ biến, nhưng Microsoft đã thực hiện một sự thúc đẩy lớn để cải thiện sự hỗ trợ kiểm thử đơn vị trong Visual Studio và giờ đây nó cũng đã được tích hợp trong các phiên bản Visual Studio miễn phí Kết quả là một framework kiểm thử đơn vị đã được tích hợp một cách chặt chẽ vào toàn bộ IDE và điều này là rất tốt

Các công cụ thứ ba tôi chọn là Moq, nó là một bộ công cụ làm giả Tôi sử dụng Moq để tạo ra các triển khai của giao diện để sử dụng trong các kiểm thử đơn vị Các lập trình viên hoặc là thích hoặc là ghét Moq; không có ai ở giữa cả Hoặc là bạn sẽ thấy thấy rằng cú pháp khá thanh lịch và biểu cảm, còn không bạn sẽ bị nguyền rủa mỗi khi bạn cố gắng sử dụng nó Nếu bạn cảm thấy không quen, tôi đề nghị bản thử Rhino Mocks, nó là một thay thế tốt

Tôi sẽ giới thiệu mỗi công cụ và trình bày các tính năng cốt lõi của chúng, nhưng tôi không đào sâu một cách đầy đủ về chúng Chi tiết về mỗi một trong số chúng đều có thể trong các sách riêng Tôi đã cho bạn đủ thông tin để bắt đầu và làm theo các ví dụ trong phần còn lại của cuốn sách Bảng 6-1 cung cấp bản tóm tắt cho chương này

Đưa các giá trị property và

constructor vào các đối tượng

mới được tạo ra

Tự động chọn một lớp

triển khai cho một

giao diện

Kiểm soát vòng đời của các đối

tượng mà Ninject tạo ra

Tạo một kiểm thử đơn vị

Kiểm tra các kết quả mong đợi

trong một kiểm thử đơn vị

Tập trung một kiểm thử đơn vị

vào một tính năng duy nhất của

Sử dụng phương thức WithPropertyValue và WithConstructorArgument

Sử dụng một ràng buộc có điều kiện của Ninject

Thiết lập một phạm vi của đối tượng Thêm một project kiểm thử đơn vị vào solution và chú thích một tập tin lớp với các thuộc tính TestClass

Trang 2

Chú ý Chương này giả định rằng bạn muốn tất cả những lợi ích có được từ MVC Framework, bao gồm một kiến trúc có hỗ trợ

rất nhiều kiểm thử và nhấn mạnh vào việc tạo ra các ứng dụng có thể dễ dàng sửa đổi và bảo trì Tôi thích thứ này, nhưng tôi biết rằng một số độc giả sẽ chỉ muốn hiểu các tính năng mà các MVC Framework cung cấp và không muốn biết về phương pháp và phương châm của việc phát triển Tôi sẽ không cố gắng thay đổi bạn Đó là quyết định cá nhân và bạn biết là bạn cần làm những gì

để phát triển các dự án của mình Tôi đề nghị chí ít là bạn nên xem lướt qua qua chương này để xem những gì có sẵn, nhưng nếu bạn không phải là người thích kiểm thử đơn vị, thì bạn có thể nhảy được tới chương kế tiếp và xem làm thế nào để xây dựng một ứng dụng MVC mẫu thực tế hơn

Chuẩn bị Project Mẫu

Tôi sẽ bắt đầu bằng cách tạo ra một project mẫu đơn giản, tên là EssentialTools, mà tôi sẽ sử dụng trong suốt chương này Tôi sử dụng template ASP.NET MVC Web Application, chọn tùy chọn Empty và đánh dấu vào ô để thêm các nội dung một project MVC cơ bản

public class Product {

public int ProductID { get; set; }

public string Name { get; set; }

public string Description { get; set; }

public decimal Price { get; set; }

public string Category { set; get; }

public class LinqValueCalculator {

public decimal ValueProducts(IEnumerable<Product> products) { return products.Sum(p => p.Price);

}

}

}

Lớp LinqValueCalculator định nghĩa một phương thức duy nhất tên là ValueProducts, nó sử dụng

phương thức Sum của LINQ để cộng giá trị của các thuộc tính Price của từng đối tượng Product thành một số đếm được (một tính năng khá tốt của LINQ mà tôi thường sử dụng)

Lớp model cuối cùng của tôi là ShoppingCart và nó đại diện cho một bộ các đối tượng Product và sử dụng một

Trang 3

LinqValueCalculator để xác định tổng giá trị Tôi đã tạo ra một tập tin lớp mới tên là ShoppingCart.cs

và thêm vào các câu lệnh như trong Liệt kê 6-3

Liệt kê 6-3 Nội dung của tập tin ShoppingCart.cs

using System.Collections.Generic;

namespace EssentialTools.Models {

public class ShoppingCart {

private LinqValueCalculator calc;

public ShoppingCart(LinqValueCalculator calcParam) {

calc = calcParam;

}

public IEnumerable<Product> Products { get; set; }

pr public decimal CalculateProductTotal() {

Tôi đã thêm một controller mới vào thư mục Controllers tên là HomeController và thiết lập nội dung khớp với

Liệt kê 6-4 Phương thức hành động của Index tạo ra một mảng các đối tượng Product và sử dụng một đối tượng LinqValueCalculator để xuất ra tổng giá trị, sau đó chuyển cho phương thức View Tôi không đặc tả một view khi tôi gọi phương thức View, vì vậy MVC Framework sẽ chọn view mặc định gắn liền với phương thức hành động (tập tin Views/Home/Index.cshtml)

public class HomeController : Controller {

private Product[] products = {

new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},

new Product {Name = "Soccer ball", Category = "Soccer", Price

= 19.50M},

= 34.95M}

};

new Product {Name = "Corner flag", Category = "Soccer", Price

public ActionResult Index() {

LinqValueCalculator calc = new LinqValueCalculator();

products };

ShoppingCart cart = new ShoppingCart(calc) { Products =

Trang 4

decimal totalValue = cart.CalculateProductTotal();

Bổ sung cuối cùng cho project là view , tên là Index Không quan trọng là bạn đã chọn tùy chọn gì khi tạo view, miễn là

bạn thiết lập nội dung giống như trong Liệt kê 6-5

Liệt kê 6-5 Nội dung của tập tin Index.cshtml

Tôi đã giới thiệu dependency injection (DI) ở Chương 3 Tóm lại, ý tưởng là để tách các thành phần trong một ứng dụng

MVC, với sự kết hợp của các interface và DI container nhằm tạo ra các instance của các đối tượng bằng cách tạo ra các

interface mà chúng phụ thuộc vào và đưa chúng vào trong constructor

Trang 5

Trong các phần tiếp theo , Tôi sẽ giải thích một vấn đề mà tôi đã cố tình tạo ra trong ví dụ của chương này và cho thấy làm thế nào để sử dụng Ninject–DI container ưa thích của tôi–để giải quyết nó Đừng lo lắng nếu bạn cảm thấy rằng mình không quen với Ninject–các nguyên tắc cơ bản là như nhau cho tất cả các DI container và có rất nhiều lựa chọn thay thế để chọn

Hiểu được Vấn đề

Trong ứng dụng mẫu, Tôi đã tạo ra một ví dụ về một vấn đề cơ bản mà DI phải xử lý: các lớp liên kết chặt chẽ với nhau Lớp ShoppingCart liên kết chặt chẽ với lớp LinqValueCalculator và lớp HomeController liên kết chặt chẽ với cả ShoppingCart và LinqValueCalculator

Điều này có nghĩa là nếu tôi muốn thay thế lớp LinqValueCalculator, Tôi phải xác định và sau đó thay đổi các tham chiếu trong các lớp được liên kết chặt chẽ với nó Đây hẳn không phải là vấn đề gì với một project đơn giản, nhưng nó sẽ trở thành một quá trình mệt nhọc và dễ bị lỗi trong một project thực tế, đặc biệt là nếu tôi muốn chuyển đổi giữa các triển khai tính toán khác nhau (ví dụ, để kiểm thử), thay vì chỉ thay thế một lớp này với một lớp khác

Áp dụng một Interface

Tôi có thể giải quyết một phần của vấn đề bằng cách sử dụng một interface C# để trừu tượng hóa định nghĩa của các chức năng tính toán từ triển khai của nó Để chứng minh điều này, tôi đã thêm tập tin lớp IValueCalculator.cs vào thư mục Models và tạo interface như trong Liệt kê 6-6

Liệt kê 6-6 Nội dung của tập tin IValueCalculator.cs

using System.Collections.Generic;

namespace EssentialTools.Models {

public interface IValueCalculator {

decimal ValueProducts(IEnumerable<Product> products);

}

}

Tôi sau đó có thể triển khai interface này trong lớp LinqValueCalculator, như trong Liệt kê 6-7

Liệt kê 6-7 Áp dụng một Interface trong tập tin LinqValueCalculator.cs

using System.Collections.Generic;

using System.Linq;

namespace EssentialTools.Models {

public class LinqValueCalculator : IValueCalculator {

public decimal ValueProducts(IEnumerable<Product> products) { return products.Sum(p => p.Price);

Trang 6

namespace EssentialTools.Models {

public class ShoppingCart {

private IValueCalculator calc;

public ShoppingCart(IValueCalculator calcParam) {

calc = calcParam;

}

public IEnumerable<Product> Products { get; set; }

public decimal CalculateProductTotal() {

return calc.ValueProducts(Products);

}

}

}

Tôi đã thực hiện một vài cải tiến, nhưng C# yêu cầu tôi đặc tả lớp triển khai cho một interface trong quá trình tạo instance, đó là

điều dễ hiểu bởi nó cần phải biết lớp triển khai nào tôi muốn sử dụng–nhưng điều đó có nghĩa là tôi vẫn còn một vấn đề trong

controller Home khi tôi tạo đối tượng LinqValueCalculator, như trong L iệt kê 6- 9

Liệt kê 6-9 Áp dụng Interface cho tập tin HomeController.cs

public ActionResult Index() {

IValueCalculator calc = new LinqValueCalculator();

ShoppingCart cart = new ShoppingCart(calc) { Products = products };

decimal totalValue = cart.CalculateProductTotal();

}

return View(totalValue);

Mục tiêu của tôi với Ninject là đạt đến điểm mà tôi xác định rằng tôi muốn khởi tạo một triển khai của interface

IValueCalculator, thế nhưng các chi tiết của việc triển khai được yêu cầu thì lại không nằm trong code của controller Home

Điều này sẽ có nghĩa là nói với Ninject rằng LinqValueCalculator là triển khai của interface IValueCalculator

mà tôi muốn nó sử dụng và cập nhật lớp HomeController để nó lấy được đối tượng của nó thông qua Ninject, thay vì sử

dụng từ khóa new

Thêm Ninject vào Visual Studio Project

Cách đơn giản nhất để thêm Ninject vào một dự án MVC là sử dụng hỗ trợ được tích hợp của Visual Studio cho NuGet, thứ giúp

việc cài đặt một loạt các gói và giữ cho chúng được cập nhật trở nên dễ dàng Tôi đã sử dụng NuGet trong Chương 2 để cài đặt

thư viện Bootstrap, nhưng có một danh mục lớn các gói có sẵn, bao gồm cả Ninject

Chọn Tools Library Package Manager Package Manager Console trong Visual Studio

để mở cửa sổ dòng lệnh NuGet và nhập vào các lệnh sau:

Install-Package Ninject -version 3.0.1.10

Install-Package Ninject.Web.Common -version 3.0.0.7

Install-Package Ninject.MVC3 -Version 3.0.0.6

Lệnh đầu tiên cài đặt các gói cốt lõi của Ninject và số còn lại thì cài đặt phần mở rộng cho lõi khiến Ninject hoạt động tốt với các ứng dụng ASP.NET (tôi sẽ giải thích ngay sau đây) Đừng hiểu nhầm với phần tham chiếu MVC3 trong tên của gói cuối cùng–

Trang 7

Tôi đã sử dụng tham số version để cài đặt các phiên bản cụ thể của các gói Đây là những phiên bản mới nhất khi tôi viết cuốn sách này Bạn nên sử dụng tham số version để đảm bảo rằng bạn sẽ có được kết quả đúng trong các ví dụ của chương này, nhưng bạn có thể bỏ qua tham số này và lấy các phiên bản mới nhất (và có khả năng gần đây hơn) cho các project thực tế

Bắt đầu với Ninject

Có ba bước để làm cho các chức năng cơ bản của Ninject hoạt động, và bạn có thể xem chúng trong Liệt kê 6-10, trong

đó cho thấy những thay đổi tôi đã thực hiện với controller Home

Mẹo Tôi sẽ đi từ từ trong phần này và các phần tiếp theo Dependency Injection có thể mất một lúc mới hiểu được và

tôi không muốn bỏ qua bất cứ điều gì có thể giúp giảm bớt sự bối rối

Liệt kê 6-10 Thêm chức năng cơ bản của Ninject vào phương thức hành động của Index trong tập tin HomeController.cs

private Product[] products = {

new Product {Name = "Kayak", Category = "Watersports", Price = new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},

new Product {Name = "Soccer ball", Category = "Soccer", Price

= 19.50M},

= 34.95M}

};

new Product {Name = "Corner flag", Category = "Soccer", Price

public ActionResult Index() {

ShoppingCart cart = new ShoppingCart(calc) { Products =

decimal totalValue = cart.CalculateProductTotal();

return View(totalValue);

}

}

}

Bước đầu tiên là chuẩn bị Ninject để sử dụng Để làm điều này, tôi tạo ra một instance của một Ninject kernel, đó là đối tượng

có trách nhiệm giải quyết các phụ thuộc và tạo ra các đối tượng mới Khi tôi cần một đối tượng, tôi sẽ sử dụng kernel thay vì từ khóa new Đây là câu lệnh tạo ra kernel từ bảng liệt kê:

Trang 8

IKernel ninjectKernel = new StandardKernel();

Tôi cần tạo ra một triển khai của interface Ninject.IKernel, bằng cách tạo ra một instance mới của lớp

StandardKernel Ninject có thể được mở rộng và tùy chỉnh để sử dụng các loại kernel khác nhau, nhưng tôi chỉ cần

StandardKernel được tích hợp sẵn trong chương này (Trên thực tế, tôi đã được sử dụng Ninject nhiều năm và tôi chỉ cần mỗi StandardKernel)

Bước thứ hai của quá trình này là cấu hình kernel của Ninject để nó hiểu các đối tượng triển khai mà tôi muốn sử dụng cho mỗi interface mà tôi đang làm việc với Đây là câu lệnh từ bảng liệt kê mà làm việc đó:

ninjectKernel.Bind< IValueCalculator >().To< LinqValueCalculator >();

Ninject sử dụng các tham số kiểu của C # để tạo ra một mối quan hệ: tôi thiết lập interface mà tôi muốn làm việc với như tham

số kiểu cho phương thức Bind và gọi phương thức To trên kết quả nó trả về Tôi thiết lập các lớp triển khai tôi muốn khởi tạo như tham số kiểu cho phương thức To Câu lệnh này nói với Ninject rằng các phụ thuộc trên interface IValueCalculator nên được giải quyết bằng cách tạo ra một instance của lớp LinqValueCalculator Bước cuối cùng là sử dụng Ninject để tạo ra một đối tượng, thông qua phương thức Get của kernel, như thế này:

Thiết lập MVC Dependency Injection

Kết quả của ba bước tôi đã giới thiệu trong phần trước là kiến thức về lớp triển khai cần được khởi tạo nhằm đáp ứng yêu cầu cho interface IValueCalculator vốn đã được thiết lập trong Ninject Tất nhiên, tôi vẫn chưa cải thiện được ứng dụng của mình vì kiến thức đó vẫn được xác định trong controller Home, có nghĩa là controller Home vẫn liên kết chặt chẽ với lớp LinqValueCalculator

Trong các phần sau, tôi sẽ chỉ cho bạn cách để nhúng Ninject vào trung tâm của ứng dụng MVC, điều này sẽ cho phép tôi đơn giản hóa các controller, mở rộng ảnh hưởng Ninject đã để nó hoạt động trên khắp ứng dụng, và di chuyển cấu hình ra khỏi controller

Tạo ra Dependency Resolver

Thay đổi đầu tiên tôi sẽ làm là tạo ra một custom dependency resolver MVC Framework sử dụng một dependency resolver để tạo

ra các instance của các lớp mà nó cần phải phục vụ các yêu cầu Bằng cách tạo ra một custom resolver, tôi có thể đảm bảo rằng MVC Framework sử dụng Ninject bất cứ khi nào nó tạo ra một đối tượng–bao gồm cả các instance của các controller, chẳng hạn

Để thiết lập resolver, Tôi đã tạo một thư mục mới có tên là Infrastructure, đó là thư mục mà tôi sử dụng để bỏ các lớp mà không phù hợp với các thư mục khác trong một ứng dụng MVC Tôi đã thêm một tập tin lớp mới vào thư mục có tên là NinjectDependencyResolver.cs, nội dung của nó bạn có thể xem trong Liệt kê 6-11

Liệt kê 6-11 Nội dung của tập tin NinjectDependencyResolver.cs

Trang 9

namespace EssentialTools.Infrastructure {

public class NinjectDependencyResolver : IDependencyResolver {

private IKernel kernel;

public NinjectDependencyResolver(IKernel kernelParam) {

}

}

Lớp NinjectDependencyResolver triển khai interface IDependencyResolver, đó là một phần của namespace System.Mvc và là cái mà MVC Framework sử dụng để lấy các đối tượng nó cần MVC Framework sẽ gọi các phương thức GetService hoặc GetServices khi nó cần một instance của một lớp để phục vụ yêu cầu vào Công việc của một dependency resolver là tạo ra instance đó, một nhiệm vụ mà tôi thực hiện bằng cách gọi phương thức TryGet và GetAll của Ninject Phương thức TryGet hoạt động như phương thức Get tôi đã sử dụng trước đó, nhưng nó sẽ trả về null khi không có ràng buộc phù hợp thay vì một exception Phương thức GetAll hỗ trợ nhiều ràng buộc cho một kiểu duy nhất, nó được sử dụng khi có một số các đối tượng triển khai khác nhau hiện hữu

Lớp dependency resolver của tôi cũng là nơi mà tôi thiết lập ràng buộc Ninject Trong phương thức AddBindings, tôi sử dụng phương thức Bind và To để cấu hình mối quan hệ giữa interface IValueCalculator và lớp LinqValueCalculator

Đăng ký cho Dependency Resolver

Nếu chỉ đơn thuần tạo một triển khai cho interface IDependencyResolver thôi thì vẫn chưa đủ–Tôi phải nói với MVC Framework rằng tôi muốn sử dụng nó Các gói Ninject tôi thêm vào bằng NuGet đã tạo ra một tập tin có tên là NinjectWebCommon.cs trong thư mục App_Start định nghĩa các phương thức được gọi một cách tự động khi ứng

dụng khởi chạy, để tích hợp vào vòng đời yêu cầu của ASP.NET (Điều này là để cung cấp tính năng phạm vi mà tôi sẽ mô tả sau

trong chương này) Trong phương thức RegisterServices của lớp NinjectWebCommon, tôi thêm một câu lệnh để tạo một instance của lớp NinjectDependencyResolver và sử dụng phương thức static SetResolver được định nghĩa bởi lớp System.Web.Mvc.DependencyResolver để đăng ký resolver với MVC Framework, như trong L iệ

t

kê 6- 1 2 Đừng lo lắng nếu cảm thấy điều này không hợp lý Tác dụng của câu lệnh này là để tạo ra một cầu nối giữa Ninject và

sự

hỗ trợ của MVC Framework cho DI

Liệt kê 6-12 Đăng ký Resolver trong tập tin NinjectWebCommon.cs

Trang 10

Tái cấu trúc lại Controller Home

Bước cuối cùng là phải tái cấu trúc lại controller Home để nó có được lợi thế của các phương tiện mà tôi thiết lập trong các phần trước Bạn có thể thấy những thay đổi mà tôi đã thực hiện trong Liệt kê 6-13

Liệt kê 6-13 Tái cấu trúc lại Controller trong tập tin HomeController.cs

using System.Web.Mvc;

using EssentialTools.Models;

namespace EssentialTools.Controllers {

public class HomeController : Controller {

private IValueCalculator calc;

private Product[] products = {

new Product {Name = "Kayak", Category = "Watersports", Price = 275M},

new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},

new Product {Name = "Soccer ball", Category = "Soccer", Price

= 19.50M},

= 34.95M}

};

new Product {Name = "Corner flag", Category = "Soccer", Price

public HomeController(IValueCalculator calcParam) {

calc = calcParam;

}

public ActionResult Index() {

products };

ShoppingCart cart = new ShoppingCart(calc) { Products =

decimal totalValue = cart.CalculateProductTotal();

Thay đổi khác mà tôi đã làm là bỏ bất kỳ đề cập nào về Ninject hoặc lớp LinqValueCalculator từ controller Sau cùng, tôi đã phá vỡ liên kết chặt chẽ giữa HomeController và lớp LinqValueCalculator

Nếu bạn chạy ví dụ này, bạn sẽ thấy kết quả như trong Hình 6-2 Tất nhiên, tôi có được kết quả tương tự khi tôi khởi tạo lớp LinqValueCalculator trực tiếp trong controller

Trang 11

Hình 6-2 Tác dụng của việc chạy ứng dụng mẫu

Tôi đã tạo một ví dụ của constructor injection, đó là một dạng của dependency injection Dưới đây là những gì đã xảy ra khi

bạn chạy ứng dụng mẫu và Internet Explorer tạo yêu cầu cho URL gốc của ứng dụng:

1 MVC Framew ork nhận yêu cầu và tìm ra rằng yêu cầu này là dành cho controller Home (Tôi sẽ giải thích MVC framew ork làm điều này như thế nào trong Chương 17)

2 MVC Framew ork yêu cầu lớp custom dependency resolver của tôi tạo một instance của lớp HomeController, đặc

tả lớp sử dụng tham số Type của phương thức GetService

3 Dependency resolver của tôi yêu cầu Ninject tạo một lớp HomeController mới, truyền đối tượng Type cho phương thức TryGet

4 Ninjec t kiểm tr a contruct or HomeController và thấy rằng nó đã khai báo một phụ thuộc vào interface IValueCalculator, mà nó đã có một ràng buộc

5 Ninjec t tạo một ins tanc e của lớp LinqValueCalculator và sử dụng nó để tạo một ins tanc e mới của lớp HomeController

6 Ninject truyền instance HomeController cho custom dependency resolver, thứ sẽ trả nó về cho MVC Framew ork MVC Framew ork sử dụng instance của controller để phục vụ yêu cầu

Tôi làm điều này tốn chút ích công sức bởi DI có thể khá lằn nhằn khi bạn thấy nó lần đầu tiên được sử dụng Một lợi ích của

cách tiếp cận tôi đã thực hiện ở đây là bất kỳ controller nào trong ứng dụng đều có thể khai báo một phụ thuộc và MVC

Framew ork sẽ sử dụng Ninject để giải quyết nó

Quan trọng nhất là tôi chỉ phải thay đổi lớp dependency resolver khi tôi muốn thay thế LinqValueCalculator mới một triển khai khác, bởi vì đây là nơi duy nhất mà tôi phải đặc tả triển khai được sử dụng để đáp ứng các phụ thuộc vào interface IValueCalculator

Tạo Các Chuỗi Phụ Thuộc

Khi bạn yêu cầu Ninject tạo một kiểu, nó kiểm tra các phụ thuộc mà kiểu này đã khai báo Nó cũng xem xét những phụ thuộc này

để xem liệu chúng dựa vào các kiểu khác–hoặc, nói cách khác, liệu chúng có khái báo các phụ thuộc của riêng chúng Nếu có phụ

thuộc bổ sung, Ninject sẽ tự động giải quyết chúng và tạo ra các instance của tất cả các lớp được yêu cầu, làm việc theo cách của

nó xuyên suốt chuỗi các phụ thuộc để sau cùng nó có thể tạo ra một instance của kiểu mà bạn đã yêu cầu

Để biểu diễn tính năng này, tôi đã thêm một tập tin có tên là Discount.cs trong thư mục Models và sử dụng nó để định nghĩa một interface mới và một lớp sẽ triển khai nó, như trong Liệt kê 6-14

Liệt kê 6-14 Nội dung của tập tin Discount.cs

namespace EssentialTools.Models {

public interface IDiscountHelper {

decimal ApplyDiscount(decimal totalParam);

}

public class DefaultDiscountHelper : IDiscountHelper {

Trang 12

public decimal ApplyDiscount(decimal totalParam) {

return (totalParam - (10m / 100m * totalParam));

Liệt kê 6-15 Thêm một phụ thuộc trong tập tin LinqValueCalculator.cs

using System.Collections.Generic;

using System.Linq;

namespace EssentialTools.Models {

public class LinqValueCalculator: IValueCalculator {

private IDiscountHelper discounter;

public LinqValueCalculator(IDiscountHelper discountParam) {

discounter = discountParam;

}

public decimal ValueProducts(IEnumerable<Product> products) {

return discounter.ApplyDiscount (products.Sum(p => p.Price));

Tôi ràng buộc interface IDiscountHelper vào lớp triển khai với kernel của Ninject trong lớp NinjectDependencyResolver, hệt như tôi đã làm với IValueCalculator, như trong Liệt kê 6-16

Liệt kê 6-16 Ràng buộc một Interface khác vào triển khai của nó trong tập tin NinjectDependencyResolver.cs

Đặc tả Thuộc tính và Các Giá trị của Tham số trong Constructor

Trang 13

Tôi có thể cấu hình các đối tượng mà Ninject tạo ra bằng cách cung cấp chi tiết về giá trị mà tôi muốn áp dụng cho các thuộc tính khi tôi ràng buộc interface vào triển khai của nó Để biểu diễn tính năng này, tôi đã sửa đổi lớp DefaultDiscountHelper

để nó định nghĩa một thuộc tính DiscountSize, mà tôi dùng để tính mức giảm giá, như trong Liệt kê 6-17

Liệt kê 6-17 Thêm một Thuộc tính trong tập tin Discount.cs

namespace EssentialTools.Models {

public interface IDiscountHelper {

decimal ApplyDiscount(decimal totalParam);

}

public class DefaultDiscountHelper : IDiscountHelper {

public decimal DiscountSize { get; set; }

public decimal ApplyDiscount(decimal totalParam) {

return (totalParam - (DiscountSize / 100m * totalParam));

Liệt kê 6-18 Chú ý rằng tôi cung cấp tên của thuộc tính để thiết lập như một giá trị chuỗi

Liệt kê 6-18 Sử dụng Phương thức WithPropertyValue của Ninject trong tập tin NinjectDependencyResolver.cs

}

Tôi không cần phải thay đổi các ràng buộc khác, cũng không cần thay đổi cách tôi sử dụng phương thức Get để có được một instance của lớp ShoppingCart Giá trị của thuộc tính được thiết lập sau khi xây dựng lớp DefaultDiscountHelper,

và có tác dụng giảm một nửa tổng giá trị của các item Hình 6-3 cho thấy kết quả của sự thay đổi này

H ình 6-3 Tác dụng của việc áp dụng giảm giá thông qua một thuộc tính khi giải quyết chuỗi phụ thuộc

Nếu bạn có nhiều hơn một giá trị thuộc tính mà bạn cần phải thiết lập , bạn có thể gọi dây chuyền phương thức WithPropertyValue để bao quát tất cả chúng Tôi có thể làm điều tương tự với các tham số constructor Liệt kê 6-

19 cho thấy lớp DefaultDiscounterHelper đã được làm lại để mức giảm giá được truyền qua như là một tham

số constructor

Trang 14

Liệt kê 6-19 Sử dụng một Thuộc tính của Constructor trong tập tin Discount.cs

namespace EssentialTools.Models {

public interface IDiscountHelper {

decimal ApplyDiscount(decimal totalParam);

}

public class DefaultDiscountHelper : IDiscountHelper {

public decimal discountSize;

public DefaultDiscountHelper(decimal discountParam) {

discountSize = discountParam;

}

public decimal ApplyDiscount(decimal totalParam) {

return (totalParam - (discountSize / 100m * totalParam));

Sử dụng Ràng buộc Có điều kiện

Ninject hỗ trợ một số các phương thức ràng buộc có điều kiện cho phép tôi để đặc tả các lớp mà kernel nên sử dụng để đáp ứng các yêu cầu dành cho các interface cụ thể Để biểu diễn tính năng này, tôi đã thêm một tập tin mới vào thư mục Models của project mẫu có tên là FlexibleDiscountHelper.cs, bạn có thể xem nội dung củ nó trong Liệt kê 6-21

Liệt kê 6-21 Nội dung của tập tin FlexibleDiscountHelper.cs

namespace EssentialTools.Models {

public class FlexibleDiscountHelper : IDiscountHelper {

public decimal ApplyDiscount(decimal totalParam) {

decimal discount = totalParam > 100 ? 70 : 25;

return (totalParam - (discount / 100m * totalParam));

}

Trang 15

}

}

Lớp FlexibleDiscountHelper áp dụng các giảm giá khác nhau dựa trên độ lớn của tổng số Bây giờ tôi có một loạt các lớp có thể chọn để triển khai interface IDiscountHelper, tôi có thể thay đổi phương thức AddBindings của NinjectDependencyResolver để báo với Ninject khi tôi muốn sử dụng mỗi một trong số chúng, như trong Liệt

Bảng 6-2 Các Phương thức Ràng buộc Có điều kiện của Ninject

Phương thức Tác dụng

When(predicate) Ràng buộc được sử dụng khi vị từ—một biểu thức lambda—trả về t rue

WhenClassHas<T>() Ràng buộc được sử dụng khi lớp đang bị inject được chú thích với thuộc tính có kiểu được quy định bởi T

WhenInjectedInto<T>() Ràng buộc được sử dụng khi lớp đang bị inject vào thuộc kiểu T

Thiết lập Phạm vi của Đối tượng

Tính năng cuối cùng của Ninject giúp quản lý vòng đời của đối tượng mà Ninject tạo ra để phù hợp với nhu cầu của ứng dụng của bạn Mặc định, Ninject sẽ tạo một instance mới của mọi đối tượng cần để giải quyết tất cả phụ thuộc mỗi khi bạn yêu cầu một đối tượng

Để minh chứng cho những gì sẽ xảy ra, Tôi đã sửa đổi constructor cho lớp LinqValueCalculator class để nó xuất một thông báo ra cửa sổ Output của Visual Studio khi một instance mới được tạo ra, như trong Liệt kệ 6-23

Liệt kê 6-23 Thêm một Constructor trong tập tin LinqValueCalculator.cs

using System.Collections.Generic;

using System.Linq;

namespace EssentialTools.Models {

public class LinqValueCalculator : IValueCalculator {

private IDiscountHelper discounter;

private static int counter = 0;

public LinqValueCalculator(IDiscountHelper discountParam) {

discounter = discountParam;

Trang 16

System.Diagnostics.Debug.WriteLine(

string.Format("Instance {0} created", ++counter));

}

public decimal ValueProducts(IEnumerable<Product> products) {

return discounter.ApplyDiscount(products.Sum(p => p.Price)); }

}

}

Lớp System.Diagnostics.Debug chứa một số phương thức có thể được sử dụng để xuất ra các thông điệp debugging và tôi thấy chúng hữu ích khi kết code để xem chúng hoạt động như thế nào Tiếc thay, tôi đã đủ già dặn nên các debugger không phức tạp khi tôi bắt đầu viết code và tôi thấy mình như trở lại với kiến thức cơ bản khi nói đến debugging

Trong Liệt kê 6-24, tôi đã sửa đổi controller Home để nó yêu cầu hai triển khai của interface IValueCalculator từ Ninject

Liệt kê 6-24 Sử dụng nhiều Instance của Lớp Calculator trong tập tin HomeController.cs

sẽ muốn tạo một instance mới cho mỗi yêu cầu HTTP mà nền tảng ASP.NET nhận được Ninject cho phép bạn kiểm soát vòng đời

của các đối tượng bạn tạo bằng một tính năng có gòi là một phạm vi, được thể hiện bằng một lời gọi phương thức khi thiết lập ràng

buộc giữa một interface và kiểu triển khai của nó Trong Liệt kê 6-25, bạn có thể thấy cách tôi áp dụng phạm vi hữu ích nhất cho các

ứng dụng MVC Framework: phạm vi yêu cầu cho lớp LinqValueCalculator trong NinjectDependencyResolver

Liệt kê 6-25 Sử dụng Phạm vi Yêu cầu trong tập tin NinjectDependencyResolver.cs

public class NinjectDependencyResolver : IDependencyResolver {

private IKernel kernel;

public NinjectDependencyResolver(IKernel kernelParam) {

kernel = kernelParam;

AddBindings();

Trang 17

public object GetService(Type serviceType) {

kernel.Bind<IDiscountHelper>()

.To<DefaultDiscountHelper> ().WithConstructorArgument("discountParam", 50M);

kernel.Bind<IDiscountHelper>().To<FlexibleDiscountHelper>() WhenInjectedInto<LinqValueCalculator>();

Bảng 6-3 Các Phương thức Phạm vi của Ninject

Kiểm thử Đơn vị với Visual Studio

Trong cuốn sách này, Tôi sử dụng hỗ trợ kiểm thử đơn vị tích hợp kèm với Visual Studio, nhưng vẫn có các gói kiểm thử đơn vị NET khác có sẵn Thông dụng nhất có lẻ là NUnit, nhưng tất cả các gói kiểm thử hầu hết đều như nhau Lý do tôi chọn công cụ kiểm thử củaVisual Studio test tools là vì tôi thích sự tích hợp với toàn bộ phần còn lại của IDE

Để biểu diễn hỗ trợ kiểm thử đơn vị của Visual Studio, tôi đã thêm một triển khai mới của interface IDiscountHelper vào project mẫu Tạo một tập tin mới trong thư mục Models có tên là MinimumDiscountHelper.cs và đảm bảo rằng nội dung của nó giống như trong Liệt kê 6-26

Liệt kê 6-26 Nội dung của tập tin MinumumDiscountHelper.cs

using System;

namespace EssentialTools.Models {

public class MinimumDiscountHelper : IDiscountHelper {

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

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w