在[ASP.NET MVC 小牛之路]系列上一篇文章(依賴注入(DI)和Ninject)的末尾提到了在ASP.NET MVC中使用Ninject要做的兩件事情,續這篇文章之後,本文將用一個實際的示例來演示Ninject在ASP.NET MVC中的應用。
為了更好的理解和撐握本文內容,強烈建議初學者閱讀本文前先閱讀依賴注入(DI)和Ninject。
本文目錄:
準備工作
新建一個名為BookShop的空白解決方案。在該解決方案中分別新增一個名為BookShop.WebUI的MVC空應用程式,和一個名為BookShop.Domain的類庫工程。目錄結構如下:
兩個工程新增完後,在BookShop.WebUI工程下新增BookShop.Domain工程的引用。
使用NuGet分別為BookShop.WebUI工程和BookShop.Domain工程安裝Ninject包(NuGet的介紹請閱讀依賴注入(DI)和Ninject)。可以通過視覺化視窗安裝,也可以開啟Package Manager Console(檢視->其他視窗->Package Manager Console)執行下面命令安裝:
Install-Package Ninject -Project BookShop.WebUI
Install-Package Ninject -Project BookShop.Domain
下圖說明安裝成功:
建立Controller Factory
我們知道,在ASP.NET MVC中,一個客戶端請求是在特定Controller的Action中進行處理的。 預設情況下,ASP.NET MVC使用內建的Controller工廠類 DefaultControllerFactory來建立某個請求對應的Controller例項。有時候預設的Controller工廠不能滿足我們實際的需求,我們就需要對這種預設行為進行擴充套件,即建立一個繼承自DefaultControllerFactory類的自定義Controller工廠類並重寫其中的一些方法。為此,我們在BookShop.WebUI工程下建立一個名為Infrastructure的資料夾,在該資料夾中新增一個名為NinjectControllerFactory的工廠類,程式碼如下:
public class NinjectControllerFactory : DefaultControllerFactory { private IKernel ninjectKernel; public NinjectControllerFactory() { ninjectKernel = new StandardKernel(); AddBindings(); } protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return controllerType == null ? null : (IController)ninjectKernel.Get(controllerType); } private void AddBindings() { // todo:後面再來新增繫結 } }
上面程式碼中的 ninjectKernel.Get(controllerType) 可獲取到一個Controller例項。在這裡如果手動例項化Controller類是一個非常複雜的過程,我們不知道Controller類有沒有帶引數的建構函式,也不知道建構函式的引數是什麼型別。而使用Ninject只需要使用上面的一個Get方法就可以,Ninject內部會自動處理所有的依賴關係,智慧地建立我們需要的物件。
Controller工廠類建立好後,我們就需要告訴MVC用我們的NinjectControllerFactory類來建立Controller物件,為此,需在Global.asax檔案的Application_Start方法中新增下面程式碼:
protected void Application_Start() { ...... //設定Controller工廠 ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory()); }
這裡我們暫且不去關心上面這段程式碼是什麼原理,知道設定自定義的Controller工廠必須要在這注冊就行了,有空的話我會在後續博文對這部分內容進行更深入的講解。
新增Domain Model
在MVC應用程式中,一切都是圍繞Domain Model(領域模型)來的。 所以我們在BookShop.Domain工程中專門建一個名為Entities的資料夾,用來存放領域實體模型。作為一個電子商務類的網上書店,當然最重要的一個領域實體就是Book了。由於只是為了演示,我們簡單定義一個Book類,並在Entities資料夾中新增該類,程式碼如下:
public class Book { public int ID { get; set; } public string Title { get; set; } public string Isbn { get; set; } public string Summary { get; set; } public string Author { get; set; } public byte[] Thumbnail { get; set; } public decimal Price { get; set; } public DateTime Published { get; set; } }
新增Repository
我們知道,我們肯定需要一種方式來從資料庫中讀取Book資料。在這我們不防為資料的使用者(這裡指Controller)提供一個IBookRepository介面,在這個介面中宣告一個IQueryable<Book>型別的屬性Books。這樣,通過該介面使用依賴注入,使用者就可以拿到Books資料集合,而不用關心資料是如何得到的。為此,我們在BookShop.Domain工程中新增一個名為 Abstract的資料夾,在該資料夾中新增我們的IBookRepository介面檔案,程式碼如下:
public interface IBookRepository { IQueryable<Book> Books { get; } }
在MVC中我們一般會用倉儲模式(Repository Pattern)把資料相關的邏輯和領域實體模型分離,這樣對於使用者來說,通過呼叫倉儲物件,使用者可以直接拿到自己想要的資料,而完全不必關心資料具體是如何來的。我們可以把倉儲比喻成一個超市,超市已經為消費者供備好了商品,消費者只管去超市選購自己需要的商品,而完全不必關心這些商品是從哪些供應商怎麼樣運輸到超市的。但對於倉儲本身,必須要實現讀取資料的“渠道”。
在BookShop.Domain工程中新增一個名為Concrete資料夾用於存放具體的類。我們在Concrete資料夾中新增一個實現了IBookRepository介面的BookRepository類來作為我們的Book資料倉儲。BookRepository類程式碼如下:
public class BookRepository : IBookRepository { public IQueryable<Book> Books { get { return GetBooks().AsQueryable(); } } private static List<Book> GetBooks() { //為了演示,這裡手工造一些資料,後面會介紹使用EF從資料庫中讀取。 List<Book> books = new List<Book>{ new Book { ID = 1, Title = "ASP.NET MVC 4 程式設計", Price = 52}, new Book { ID = 2, Title = "CLR Via C#", Price = 46}, new Book { ID = 3, Title = "平凡的世界", Price = 37} }; return books; } }
為了演示,上面是手工造的一些資料,後面的文章我將介紹使用Entity Framwork從資料庫中讀取資料。對於剛接觸ORM框架的朋友可能對這裡IQueryable感到奇怪,為什麼用IQueryable作為返回型別,而不用IEnumerable呢?後面有機會講Entity Framwork的時候再講。
新增繫結
開啟之前我們在BookShop.WebUI工程建立的NinjectControllerFactory類,在AddBindings方法中新增如下程式碼:
private void AddBindings() { ninjectKernel.Bind<IBookRepository>().To<BookRepository>(); }
這句程式碼,通過Ninject把IBookRepository介面繫結到BookRepository,當IBookRepository介面的實現被請求時,Ninject將自動建立BookRepository類的例項。
到這裡,Ninject的使用步驟就結束了,接下來我們把本示例剩餘的步驟完成。
顯示列表
右擊BookShop.WebUI工程的Controllers資料夾,新增一個名為Book的Controller,按下面程式碼對其進行編輯:
public class BookController : Controller { private IBookRepository repository; public BookController(IBookRepository bookRepository) { repository = bookRepository; } }
在這,BookController的建構函式接受了一個IBookRepository引數,當BookController被例項化的時候,Ninject就為其注入了BookRepository的依賴。接下來我們為這個Controller新增一個名為List的Action,用來呈現Book列表。程式碼如下:
public class BookController : Controller { ... public ViewResult List() { return View(repository.Books); } }
當然我們需要新增一個View。右擊上面的List方法,選擇新增檢視,在彈出的視窗進行如下配置:
然後我們在List.cshtml中用foreach迴圈來列舉書本資訊,程式碼如下:
@model IEnumerable<BookShop.Domain.Entities.Book> @{ ViewBag.Title = "Books"; } @foreach (var p in Model) { <div class="item" style="border-bottom:1px dashed silver;"> <h3>@p.Title</h3> <p>價格:@p.Price.ToString("c") </p> </div> }
最後我們還需要修改一下預設路由,讓系統執行後直接導向到我們的{controller = "Book", action = "List"},開啟Global.asax檔案,找到RegisterRoutes方法,進行如下修改:
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Book", action = "List", id = UrlParameter.Optional } ); }
到這,我們的程式可以執行了,效果如下:
結束語:
本文是Ninject在ASP.NET MVC中使用的一個簡單示例,目的是讓大家瞭解Ninject在MVC中的使用方法。當然,Ninject的強大之處不僅限於本文所演示的,相信當你熟悉了Niject之後,在搭建MVC應用程式時,你一定會喜歡上它的。
評論精選
@Liam Wang
不需要啊,只需要在Application_Start()函式中註冊一下:
DependencyResolver.SetResolver(new Code.NinjectDependencyResolver());//註冊Ioc容器
然後在具體使用中,Controller構造注入或者使用屬性注入即可
相關程式碼:namespace LvJl.WebMvc.Code { public class NinjectDependencyResolver:System.Web.Mvc.IDependencyResolver { private readonly IKernel _kernel; public NinjectDependencyResolver() { _kernel=new StandardKernel(); AddBindings(); } private void AddBindings() { _kernel.Bind<IUserService>().To<UserService>(); _kernel.Bind<IRoleService>().To<RoleService>(); } public object GetService(Type serviceType) { return _kernel.TryGet(serviceType); } public IEnumerable<object> GetServices(Type serviceType) { return _kernel.GetAll(serviceType); } } } protected void Application_Start() { DependencyResolver.SetResolver(new Code.NinjectDependencyResolver());//註冊Ioc容器 AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } public class UserController : Controller { private readonly IUserService _user; public UserController(IUserService userService) { _user = userService; } public ActionResult Index() { return View(); } public ActionResult GetAllUsers() { var pageIndex = Request["page"] == null ? 1 : int.Parse(Request["page"]); var pageSize = Request["rows"] == null ? 1 : int.Parse(Request["rows"]); int total; var data = _user.Query(pageIndex, pageSize, out total, u => true, true, u => u.Id); var result = new {total, rows = data}; return Json(result, JsonRequestBehavior.AllowGet); } }
參考:《Pro ASP.NET MVC 4》
BookController類的建立(含初始化)主要經過下面這三個過程:
1.在Application_Start中,ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());這段註冊程式碼告訴MVC用NinjectControllerFactory工廠類來建立所有Controller物件。
在NinjectControllerFactory類中包含了下面兩個過程:繫結介面到介面的實現和建立Controller類物件。
2.ninjectKernel.Bind<IBookRepository>().To<BookRepository>();這段繫結程式碼告訴ninjectKernel當被請求一個IBookRepository介面的實現時則返回一個BookRepository物件。
3.請你閱讀NinjectControllerFactory類中的GetControllerInstance方法,通過ninjectKernel.Get(controllerType)這句程式碼,ninject獲取controller(如BookController)物件的資訊並建立該controller的例項,這個過程會呼叫controller的建構函式,它會自動判斷建構函式所需要的引數,如BookController類的建構函式需要一個IBookRepository介面引數,根據第2個過程ninject註冊的繫結,ninject會給該建構函式傳遞BookRepository物件(IBookRepository介面的實現者)的引用。