再次調整項目架構是由於和羣友dezhou的一次聊天,我原來的想法是項目儘可能作簡單點別搞太複雜了,僅使用了DbContext的注入,其餘的也沒有寫接口耦合度很高。和dezhou聊過以後我仔細考慮了一下,仍是解耦吧,原本按照軟件設計模式就應該是高內聚低耦合的,低耦合使項目的模塊獨立於其餘模塊,增長了可維護性和移植性!html
注:前面寫的博客詳細記錄沒項目操做的每一步,其實寫起博客來很費時間,並且整片博文裏不少無用的信息。對MVC來講會添加控制器,添加視圖,添加類這些都最基本的要求了,而且前面博文裏都寫了,後面也就再也不詳細寫這些東西了,主要寫一些思路和關鍵代碼,具體內容以源代碼的形式放在博客後面提供下載。git
咱們看一下,vs2015默認生成的項目結構。數據庫
項目中模型、數據訪問、業務邏輯和視圖相關的內容都在一個項目中,視圖、業務邏輯和顯示牢牢耦合,前期看着還沒什麼,到了內容多了項目變大之後,尤爲是隔一段時間再更新項目,在看的話一片混亂,有時候一個小的改動形成整個項目導出報錯,頭痛之極。設計模式
咱們再看看三層架構:瀏覽器
三層架構主要是使項目結構更清楚,分工更明確,有利於後期的維護和升級。它未必會提高性能,由於當子程序模塊未執行結束時,主程序模塊只能處於等待狀態。這說明將應用程序劃分層次,會帶來其執行速度上的一些損失。但從團隊開發效率角和維護性上來講易於進行任務分配,可維護性高。架構
按照三層的思想,MVC中的控制器(C)和視圖(V)都是處理界面顯示相關的內容屬於用戶界面表示層(USL) ,模型(M)是控制器和視圖間交換的數據,因此MVC框架應該都屬於三層中的用戶界面表示層。 框架
數據訪問層(DAL)和業務邏輯層(BLL) 、業務邏輯層和用戶界面表示層(USL) 也要交換數據,乾脆把模型(M)獨立出來,做爲控制器和視圖,及三個層次之間交換的數據。 ide
咱們看向Ninesky如今的項目結構,以下圖: 函數
包含四個項目: post
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 }
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
在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 }
在代碼開始處聲明瞭變量類型爲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 }
服務定位器的好處是實現比較簡單,能夠建立一個全局的服務定位器,缺點就是組件需求不透明。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 }
而後咱們進入,Web的啓動類Startup進行注入。以下圖:
第一個紅框內是在《2.一、欄目的前臺顯示》中注入的上下文;
第二個紅框到第四個紅框內是今天添加的內容。
第三個紅框內注入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瀏覽器中查看一下,能夠看到取出了數據,只是由於數據存儲層的代碼沒有包含導航屬性因此數據不完整。
文章發佈地址:http://www.ninesky.cn
代碼包下載:Ninesky2.3項目架構調整-控制反轉和依賴注入的使用.rar