.Net Core MVC 網站開發(Ninesky) 2.3、項目架構調整-控制反轉和依賴註入的使用

来源:http://www.cnblogs.com/mzwhj/archive/2016/12/19/6196153.html
-Advertisement-
Play Games

再次調整項目架構是因為和群友dezhou的一次聊天,我原來的想法是項目儘量做簡單點別搞太複雜了,僅使用了DbContext的註入,其他的也沒有寫介面耦合度很高。和dezhou聊過之後我仔細考慮了一下,還是解耦吧,本來按照軟體設計模式就應該是高內聚低耦合的,低耦合使項目的模塊獨立於其他模塊,增加了可維... ...


再次調整項目架構是因為和群友dezhou的一次聊天,我原來的想法是項目儘量做簡單點別搞太複雜了,僅使用了DbContext的註入,其他的也沒有寫介面耦合度很高。和dezhou聊過之後我仔細考慮了一下,還是解耦吧,本來按照軟體設計模式就應該是高內聚低耦合的,低耦合使項目的模塊獨立於其他模塊,增加了可維護性和移植性!

:前面寫的博客詳細記錄沒項目操作的每一步,其實寫起博客來很費時間,而且整片博文里很多無用的信息。對MVC來說會添加控制器,添加視圖,添加類這些都最基本的要求了,並且前面博文里都寫了,後面也就不再詳細寫這些東西了,主要寫一些思路和關鍵代碼,具體內容以源代碼的形式放在博客後面提供下載。

 

一、預設項目結構

我們看一下,vs2015預設生成的項目結構。

image

項目中模型、數據訪問、業務邏輯和視圖相關的內容都在一個項目中,視圖、業務邏輯和顯示緊緊耦合,前期看著還沒什麼,到了內容多了項目變大以後,尤其是隔一段時間再更新項目,在看的話一片混亂,有時候一個小的改動造成整個項目導出報錯,頭痛之極。

二、三層架構

我們再看看三層架構:

  • 用戶界面表示層(USL)
  • 業務邏輯層(BLL)
  • 數據訪問層(DAL)

三層架構主要是使項目結構更清楚,分工更明確,有利於後期的維護和升級。它未必會提升性能,因為當子程式模塊未執行結束時,主程式模塊只能處於等待狀態。這說明將應用程式劃分層次,會帶來其執行速度上的一些損失。但從團隊開發效率角和維護性上來說易於進行任務分配,可維護性高。

按照三層的思想,MVC中的控制器(C)和視圖(V)都是處理界面顯示相關的內容屬於用戶界面表示層(USL) ,模型(M)是控制器和視圖間交換的數據,所以MVC框架應該都屬於三層中的用戶界面表示層。

數據訪問層(DAL)和業務邏輯層(BLL) 、業務邏輯層和用戶界面表示層(USL) 也要交換數據,乾脆把模型(M)獨立出來,作為控制器和視圖,及三個層次之間交換的數據。

三、高耦合

我們看向Ninesky現在的項目結構,如下圖:

image

包含四個項目:

Ninesky.DataLibrary是數據訪問層,提供資料庫訪問的支持。

Ninesky.Base 是業務邏輯層,負責業務邏輯的處理。

Ninesky.Web 用戶界面表示層(USL),負責顯示頁面和顯示項目的邏輯處理。

Ninesky.Models 就是各層之間交換的數據實體。

從以上可以看到項目按照三層的思想進行了分層。PS:有群友問為什麼項目名稱叫DataLibrary、Base,不叫DAL,BLL?這可能是強迫症的原因,我反正看著DAL,BLL的項目名稱特別不舒服,改了個自己喜歡的名字,其實功能都一樣的。

再看一下項目的調用

看一下Ninesky.Base的CategoryService類。

image

代碼中位置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類的構造函數。

image

構造函數傳遞了一個介面類型的參數,現在類中完全和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進行註入。如下圖:

image

 

第一個紅框內是在《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瀏覽器中查看一下,可以看到取出了數據,只是因為數據存儲層的代碼沒有包含導航屬性所以數據不完整。

image

八、其他

代碼托管地址:https://git.oschina.net/ninesky/Ninesky

文章發佈地址:http://www.ninesky.cn

                 http://mzwhj.cnblogs.com/

代碼包下載:Ninesky2.3項目架構調整-控制反轉和依賴註入的使用.rar

 

返回目錄


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.首先運行如下命令 sudo apt-get install wget bc build-essential gawk genisoimage 2.下載如下資源,make all 即可 http://pan.baidu.com/s/1nvc09yp 本來有此兩條已經足夠了,發佈時卻來了個不足 15 ...
  • 隨著我們使用電腦的時間越來越久,電腦C盤的空間會出現不夠用的情況,這時我們需要的就是增加C盤的大小,基本上有兩種方式 1.通過系統自帶的磁碟管理(有可能沒法操作,主要介紹第二種) 2.通過分區軟體進行磁碟調整 1.直接百度diskgenius,下載最新的軟體 2.參照http://jingyan.b ...
  • 打開伺服器系統c盤,打開window, 右鍵temp 屬性 安全 編輯 添加IIS_IUSRS 用戶控制許可權添加修改和寫入許可權即可。這是Windows Server 2008 R2 標準版 SP1 64位中文版解決辦法。 ...
  • 1、控制面板》程式和功能》打開或關閉Windows功能 > Internet信息服務 > 萬維網服務 > 應用程式開發功能 > ASP.NET(看這個是否選上); 2、“處理程式映射”中缺少ASP.NET 4.0的映射,需要添加映射。操作方法:在管理員身份打開命令行,運行以下命令: C:\Windo ...
  • 裝箱和拆箱 目錄 性能 裝箱 拆箱 介紹 裝箱是將值類型轉換為 object 類型或由此值類型實現的任何介面類型的一個過程。 當 CLR 對值類型進行裝箱時,會將該值包裝到 System.Object 內部,再將後者存儲在托管堆上。 拆箱將從對象中提取值類型。 裝箱是隱式的;拆箱是顯式的。 裝箱和拆 ...
  • 異常介紹 C# 語言的異常處理功能可幫助您處理程式運行時出現的任何意外或異常情況。 異常處理使用 try、catch 和 finally 關鍵字嘗試某些操作,以處理失敗情況,儘管這些操作有可能失敗,但如果您確定需要這樣做,且希望在事後清理資源,就可以嘗試這樣做。 公共語言運行時 (CLR)、.NET ...
  • 學會處理異常 C# 程式員可使用 try 塊對可能受異常影響的代碼進行分區。 關聯的 catch 塊用於處理任何結果異常。 一個包含代碼的 finally 塊,無論 try 塊中是否引發異常(例如,釋放在 try 塊中分配的資源),這些代碼都會運行。 一個 try 塊需要一個或多個關聯的 catch ...
  • 學會使用異常 在 C# 中,程式中的運行時錯誤通過使用一種稱為“異常”的機制在程式中傳播。 異常由錯誤的代碼引發,並由能夠更正錯誤的代碼進行捕捉。 異常可由 .NET 的公共語言運行時 (CLR) 或由程式中的代碼引發。 一旦引發了一個異常,這個異常就會在調用堆棧中向上傳播,直到找到針對它的 cat ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...