標籤:提升 dex 架構 src ini view 新項目 執行 表達
再次調整項目架構是因為和群友dezhou的一次聊天,我原來的想法是項目盡量做簡單點別搞太複雜了,僅使用了DbContext的注入,其他的也沒有寫介面耦合度很高。和dezhou聊過之後我仔細考慮了一下,還是解耦吧,本來按照軟體設計模式就應該是高內聚低耦合的,低耦合使項目的模組獨立於其他模組,增加了可維護性和移植性!
注:前面寫的部落格詳細記錄沒項目操作的每一步,其實寫起部落格來很費時間,而且整片博文裡很多無用的資訊。對MVC來說會添加控制器,添加視圖,添加類這些都最基本的要求了,並且前面博文裡都寫了,後面也就不再詳細寫這些東西了,主要寫一些思路和關鍵代碼,具體內容以原始碼的形式放在部落格後面提供下載。
一、預設項目結構
我們看一下,vs2015預設產生的項目結構。
項目中模型、資料訪問、商務邏輯和視圖相關的內容都在一個項目中,視圖、商務邏輯和顯示緊緊耦合,前期看著還沒什麼,到了內容多了項目變大以後,尤其是隔一段時間再更新項目,在看的話一片混亂,有時候一個小的改動造成整個項目匯出報錯,頭痛之極。
二、三層架構
我們再看看三層架構:
- 使用者介面展示層(USL)
- 商務邏輯層(BLL)
- 資料訪問層(DAL)
三層架構主要是使項目結構更清楚,分工更明確,有利於後期的維護和升級。它未必會提升效能,因為當子程式模組未執行結束時,主程式模組只能處於等待狀態。這說明將應用程式劃分層次,會帶來其執行速度上的一些損失。但從團隊開發效率角和維護性上來說易於進行任務分配,可維護性高。
按照三層的思想,MVC中的控制器(C)和視圖(V)都是處理介面顯示相關的內容屬於使用者介面展示層(USL) ,模型(M)是控制器和視圖間交換的資料,所以MVC架構應該都屬於三層中的使用者介面展示層。
資料訪問層(DAL)和商務邏輯層(BLL) 、商務邏輯層和使用者介面展示層(USL) 也要交換資料,乾脆把模型(M)獨立出來,作為控制器和視圖,及三個層次之間交換的資料。 三、高耦合
我們看向Ninesky現在的項目結構,如:
包含四個項目:
Ninesky.DataLibrary是資料訪問層,提供資料庫訪問的支援。
Ninesky.Base 是商務邏輯層,負責商務邏輯的處理。
Ninesky.Web 使用者介面展示層(USL),負責顯示頁面和顯示項目的邏輯處理。
Ninesky.Models 就是各層之間交換的資料實體。
從以上可以看到項目按照三層的思想進行了分層。PS:有群友問為什麼項目名稱叫DataLibrary、Base,不叫DAL,BLL?這可能是強迫症的原因,我反正看著DAL,BLL的項目名稱特別不舒服,改了個自己喜歡的名字,其實功能都一樣的。
再看一下項目的調用
看一下Ninesky.Base的CategoryService類。
代碼中位置1聲明了類CategoryRepository,這個類是 Ninesky.DataLibrary中的一個類。位置2將這個項目執行個體化了,在位置3處我們直接調用了這個類的Find方法。從上面可以看出CategoryService類是依賴CategoryRepository類的;Ninesky.Base項目是依賴於Ninesky.DataLibrary項目的。一個項目的類精確的調用了另一個項目類的方法那麼他們之間就是高耦合。發生高耦合就是軟體設計有問題,就要解耦,把依賴實現代碼轉換成依賴邏輯,這時候就要引入抽象層(通常是介面)。 四、依賴介面
我們添加一個dll項目Ninesky.InterfaceDataLibrary,給Ninesky.DataLibrary添加對Ninesky.InterfaceDataLibrary項目的引用。
在Ninesky.InterfaceDataLibrary項目添加InterfaceBaseRepository介面
1 using System; 2 using System.Collections.Generic; 3 using System.Linq.Expressions; 4 using System.Threading.Tasks; 5 6 namespace Ninesky.InterfaceDataLibrary 7 { 8 /// <summary> 9 /// 倉儲基類介面 10 /// </summary> 11 /// <typeparam name="T"></typeparam> 12 public interface InterfaceBaseRepository<T> where T : class 13 { 14 /// <summary> 15 /// 查詢[不含導覽屬性] 16 /// </summary> 17 /// <param name="predicate">查詢運算式</param> 18 /// <returns>實體</returns> 19 T Find(Expression<Func<T, bool>> predicate); 20 } 21 }View Code修改BaseRepository代碼,讓BaseRepository繼承InterfaceBaseRepository
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Linq.Expressions; 5 using Microsoft.EntityFrameworkCore; 6 using Ninesky.InterfaceDataLibrary; 7 8 namespace Ninesky.DataLibrary 9 { 10 /// <summary> 11 /// 倉儲基類 12 /// </summary> 13 public class BaseRepository<T> :InterfaceBaseRepository<T> where T : class
14 { 15 protected DbContext _dbContext; 16 public BaseRepository(DbContext dbContext) 17 { 18 _dbContext = dbContext; 19 } 20 21 /// <summary> 22 /// 查詢[不含導覽屬性] 23 /// </summary> 24 /// <param name="predicate">查詢運算式</param> 25 /// <returns>實體</returns> 26 public virtual T Find(Expression<Func<T, bool>> predicate) 27 { 28 return _dbContext.Set<T>().SingleOrDefault(predicate); 29 } 30 } 31 } 32 View Code
在Ninesky.Base項目中引用Ninesky.InterfaceDataLibrary,我們在修改CategoryService代碼
1 public class CategoryService 2 { 3 private InterfaceBaseRepository<Category> _categoryRepository; 4 public CategoryService(DbContext dbContext) 5 { 6 _categoryRepository = new BaseRepository<Category>(dbContext); 7 } 8 9 /// <summary> 10 /// 尋找 11 /// </summary> 12 /// <param name="Id">欄目Id</param> 13 /// <returns></returns> 14 public Category Find(int Id) 15 { 16 return _categoryRepository.Find(c => c.CategoryId == Id); 17 } 18 }View Code
在代碼開始處聲明了變數類型為InterfaceBaseRepository的變數,在建構函式中將InterfaceBaseRepository執行個體化為BaseRepository類型。
現在Ninesky.Base項目依然Ninesky.DataLibrary項目進行了依賴,並沒有進行解耦,如果要想解除多Ninesky.DataLibrary的依賴就要想辦法把介面的執行個體化轉移到項目之外去。
五、控制反轉
控制反轉就是把依賴的建立移到類的外部。那麼我們修改CategoryService類的建構函式。
建構函式傳遞了一個介面類型的參數,現在類中完全和Ninesky.DataLibrary沒有了關係,可以刪除對Ninesky.DataLibrary項目的引用了。
那現在又有了一個新的問題:控制反轉如何?,怎麼進行介面的執行個體化?
常用的解決方案有服務定位器和依賴注入。
六、服務定位器
服務定位器就是在類中集中進行執行個體化。
單獨建立一個項目,添加對項目的引用,然後再工廠類中集中進行執行個體化。
1 public class Factory 2 { 3 public InterfaceBaseRepository<Category> GetBaseRepository() 4 { 5 return new BaseRepository<Category>(); 6 } 7 }View Code
服務定位器的好處是實現比較簡單,可以建立一個全域的服務定位器,缺點就是組件需求不透明。Ninesky採用另一種控制反轉的實現:依賴注入。
七、依賴注入。
以前.Net MVC中注入挺麻煩的,幸好.Net Core MVC中內建了依賴注入的支援。
修改CategoryController代碼,使用建構函式注入。這裡為了例子的簡單在控制器中直接使用資料存放區層的類進行注入,而沒有使用商務邏輯層的類。
控制器中採用建構函式注入,建構函式中傳遞CategoryService參數。
1 public class CategoryController : Controller 2 { 3 /// <summary> 4 /// 資料內容 5 /// </summary> 6 private NineskyDbContext _dbContext; 7 8 /// <summary> 9 /// 欄目服務 10 /// </summary> 11 private CategoryService _categoryService; 12 13 public CategoryController(CategoryService categoryService) 14 { 15 _categoryService = categoryService; 16 } 17 18 /// <summary> 19 /// 查看欄目 20 /// </summary> 21 /// <param name="id">欄目Id</param> 22 /// <returns></returns> 23 [Route("/Category/{id:int}")] 24 public IActionResult Index(int id) 25 { 26 var category = _categoryService.Find(id); 27 if (category == null) return View("Error", new Models.Error { Title = "錯誤訊息", Name="欄目不存在", Description="訪問ID為【"+id+"】的欄目時發生錯誤,該欄目不存在。" }); 28 switch (category.Type) 29 { 30 case CategoryType.General: 31 if (category.General == null) return View("Error",new Models.Error { Title="錯誤訊息", Name="欄目資料不完整",Description="找不到欄目【"+category.Name+"】的詳細資料。" }); 32 return View(category.General.View, category); 33 case CategoryType.Page: 34 if (category.Page == null) return View("Error", new Models.Error { Title = "錯誤訊息", Name = "欄目資料不完整", Description = "找不到欄目【" + category.Name + "】的詳細資料。" }); 35 return View(category.Page.View, category); 36 case CategoryType.Link: 37 if (category.Link == null) return View("Error", new Models.Error { Title = "錯誤訊息", Name = "欄目資料不完整", Description = "找不到欄目【" + category.Name + "】的詳細資料。" }); 38 return Redirect(category.Link.Url); 39 default: 40 return View("Error", new Models.Error { Title = "錯誤訊息", Name = "欄目資料錯誤", Description = "欄目【" + category.Name + "】的類型錯誤。" }); 41 42 } 43 } 44 }View Code
然後我們進入,Web的啟動類Startup進行注入。如:
第一個紅框內是在《2.1、欄目的前台顯示》中注入的上下文;
第二個紅框到第四個紅框內是今天添加的內容。
第三個紅框內注入InterfaceBaseRepository介面,使用BaseRepository進行執行個體化。
有第二個紅框的內容是因為BaseRepository執行個體化時有一個DbContext類型的參數。在注入的時候要求用到的參數必須要在前面注入,並且系統並不會自動吧NineskyDbContext轉換為DbContext。所以必須注入一個DbContext類型的參數。
第四個紅框是注入CategoryService。這裡CategoryService同樣可以使用介面,時間原因沒寫。
至此可以看到,CategoryService解除了對BaseRepository的依賴,在Ninesky.Base項目中沒有對Ninesky.DataLibrary進行任何的依賴,類的執行個體化是在Web項目中進行注入的,Web項目對Ninesky.DataLibrary進行了依賴。同樣的方法也可以實現Web項目對Ninesky.Base項目的解耦。
如果要完全解除Ninesky.Web項目對Ninesky.DataLibrary和Ninesky.Base項目的依賴,可以使用設定檔載入,這次先不寫了。
F5瀏覽器中查看一下,可以看到取出了資料,只是因為資料存放區層的代碼沒有包含導覽屬性所以資料不完整。
八、其他代碼託管地址:https://git.oschina.net/ninesky/Ninesky
文章發布地址:http://www.ninesky.cn
http://mzwhj.cnblogs.com/
程式碼封裝下載:Ninesky2.3項目架構調整-控制反轉和依賴注入的使用.rar
返回目錄
.Net Core MVC 網站開發(Ninesky) 2.3、項目架構調整-控制反轉和依賴注入的使用