在正式進入主題之前我們來看下幾個概念:
一、依賴倒置
依賴倒置是編程五大原則之一,即:
1、上層模塊不應該依賴于下層模塊,它們共同依賴于一個抽象。
2、抽象不能依賴于具體,具體依賴于抽象。
其中上層就是指使用者,下層就是指被使用者。
二、IoC控制反轉
控制反轉(IoC,全稱Inversion of Control)是一種思想,所謂“控制反轉”,就是反轉獲得依賴對象的過程。
三、依賴注入(DI)
依賴注入設計模式是一種在類及其依賴對象之間實現控制反轉(IoC)思想的技術。
所謂依賴注入(DI,全稱Dependency Injection),就是由IoC容器在運行期間,動態地將某種依賴關系注入到對象之中。
依賴注入主要分為3種:構造函數注入、屬性注入、方法注入。
?
這里就不做過多的描述,如果有機會會通過具體例子再和大家分享,下面我們正式進入本章主題。
PS:ASP.NET Core 內置的IoC容器目前只支持構造函數注入,以下我們也主要講解構造函數注入的方式。
話不多說,直入主題看我們的解決方案結構:
分別對上面的工程進行簡單的說明:
1、TianYa.DotNetShare.Model:為demo的實體層
2、TianYa.DotNetShare.Repository:為demo的倉儲層即數據訪問層
3、TianYa.DotNetShare.Service:為demo的服務層即業務邏輯層
4、TianYa.DotNetShare.CommTool:為demo的公共工具類庫
5、TianYa.DotNetShare.SharpCore:為demo的Sharp核心類庫
6、TianYa.DotNetShare.CoreMvcDemo:為demo的web層項目,MVC框架
約定:
1、公共的類庫,我們選擇.NET Standard 2.0作為目標框架,可與Framework進行共享。
2、本demo的web項目為ASP.NET Core Web 應用程序(.NET Core 2.2) MVC框架。
一、實體層
1、新建一個學生實體?Student
using System; using System.Collections.Generic; using System.Text;namespace TianYa.DotNetShare.Model {/// <summary>/// 學生類/// </summary>public class Student{/// <summary>/// 學號/// </summary>public string StuNo { get; set; }/// <summary>/// 姓名/// </summary>public string Name { get; set; }/// <summary>/// 年齡/// </summary>public int Age { get; set; }/// <summary>/// 性別/// </summary>public string Sex { get; set; }} }
demo中的實體就這樣了
二、倉儲層
本demo的倉儲層需要引用我們的實體層TianYa.DotNetShare.Model
為什么選擇用倉儲,原因很簡單,方便我們進行個性化擴展。在數據操作的底層進行其他個性化邏輯處理。
約定:
1、接口的定義放在根目錄下,接口的實現類,統一放到Impl文件夾,表示實現類目錄。
2、每個實體,對應一個倉儲的接口和實現類,即有多少個實體,就對應創建多少個接口和實現類。
3、倉儲層接口都以“I”開頭,以“Repository”結尾。倉儲層實現都以“Repository”結尾。
我們新建一個Student的倉儲接口IStudentRepository.cs
using System; using System.Collections.Generic; using System.Text;using TianYa.DotNetShare.Model;namespace TianYa.DotNetShare.Repository {/// <summary>/// 學生類倉儲層接口/// </summary>public interface IStudentRepository{/// <summary>/// 根據學號獲取學生信息/// </summary>/// <param name="stuNo">學號</param>/// <returns>學生信息</returns>Student GetStuInfo(string stuNo);} }
接著在Impl中新建一個Student的倉儲實現StudentRepository.cs
using System; using System.Collections.Generic; using System.Text;using TianYa.DotNetShare.Model;namespace TianYa.DotNetShare.Repository.Impl {/// <summary>/// 學生類倉儲層/// </summary>public class StudentRepository : IStudentRepository{/// <summary>/// 根據學號獲取學生信息/// </summary>/// <param name="stuNo">學號</param>/// <returns>學生信息</returns>public Student GetStuInfo(string stuNo){//數據訪問邏輯,此處為了演示就簡單些var student = new Student();switch (stuNo){case "10000":student = new Student() { StuNo = "10000", Name = "張三", Sex = "男", Age = 20 };break;case "10001":student = new Student() { StuNo = "10001", Name = "錢七七", Sex = "女", Age = 18 };break;case "10002":student = new Student() { StuNo = "10002", Name = "李四", Sex = "男", Age = 21 };break;default:student = new Student() { StuNo = "10003", Name = "王五", Sex = "男", Age = 25 };break;}return student;}} }
該類實現了IStudentRepository接口
三、服務層
本demo的服務層需要引用我們的實體層TianYa.DotNetShare.Model和我們的倉儲層TianYa.DotNetShare.Repository
服務層與倉儲層類似,它屬于倉儲層的使用者。定義的方式也與倉儲層類似,有接口和Impl實現目錄。
但服務層不需要一個實體對應一個,服務層更多的是按照功能模塊進行劃分,比如一個登錄模塊,創建一個LoginService。
約定:
1、服務層接口都以“I”開頭,以“Service”結尾。服務層實現都以“Service”結尾。
為了演示,我們新建一個Student的服務層接口IStudentService.cs
using System; using System.Collections.Generic; using System.Text;using TianYa.DotNetShare.Model;namespace TianYa.DotNetShare.Service {/// <summary>/// 學生類服務層接口/// </summary>public interface IStudentService{/// <summary>/// 根據學號獲取學生信息/// </summary>/// <param name="stuNo">學號</param>/// <returns>學生信息</returns>Student GetStuInfo(string stuNo);} }
接著我們同樣在Impl中新建一個Student的服務層實現StudentService.cs
using System; using System.Collections.Generic; using System.Text;using TianYa.DotNetShare.Model; using TianYa.DotNetShare.Repository;namespace TianYa.DotNetShare.Service.Impl {/// <summary>/// 學生類服務層/// </summary>public class StudentService : IStudentService{/// <summary>/// 定義倉儲層學生抽象類對象/// </summary>protected IStudentRepository StuRepository;/// <summary>/// 空構造函數/// </summary>public StudentService() { }/// <summary>/// 構造函數/// </summary>/// <param name="stuRepository">倉儲層學生抽象類對象</param>public StudentService(IStudentRepository stuRepository){this.StuRepository = stuRepository;}/// <summary>/// 根據學號獲取學生信息/// </summary>/// <param name="stuNo">學號</param>/// <returns>學生信息</returns>public Student GetStuInfo(string stuNo){var stu = StuRepository.GetStuInfo(stuNo);return stu;}} }
該類實現了IStudentService接口
四、公共工具類庫
公共工具類庫就是將來我們要在里面寫各種各樣的幫助類以提高程序的可復用性,此處就不做贅述。
五、Sharp核心類庫
需要從NuGet上引用以下幾個程序集:
Sharp核心類庫為公共的基礎類,最底層。
其中Model文件夾為實體目錄,主要存放數據庫連接相關的實體。Extensions文件夾為擴展目錄,主要存放最底層的擴展類,我們底層的批量依賴注入就放在這里面。
在Model實體目錄中我們新建一個用于數據庫連接的接口IDataBaseSetting.cs
using System; using System.Collections.Generic; using System.Text;namespace TianYa.DotNetShare.SharpCore.Model {public interface IDataBaseSetting{/// <summary>/// 訪問數據庫連接串/// </summary>string ConnectionString { get; set; }/// <summary>/// 數據庫名稱,當是關系型數據庫時,DatabaseName屬性沒用到/// </summary>string DatabaseName { get; set; }} }
接著添加一個用于數據庫連接的實現類DataBaseSetting.cs
using System; using System.Collections.Generic; using System.Text;namespace TianYa.DotNetShare.SharpCore.Model {public class DataBaseSetting : IDataBaseSetting{/// <summary>/// 訪問數據庫連接串/// </summary>public string ConnectionString { get; set; }/// <summary>/// 數據庫名稱,當是關系型數據庫時,DatabaseName屬性沒用到/// </summary>public string DatabaseName { get; set; }} }
該類實現了IDataBaseSetting.cs接口
Model實體目錄主要用于以后涉及到數據庫訪問的時候使用,本demo主要為了簡單介紹下如何使用ASP.NET Core內置的IoC容器DI進行批量依賴注入,故沒有對該實體目錄進行詳細的講解。
接下來就是重頭戲了,我們在Extensions擴展目錄中添加一個用于批量依賴注入的擴展類ServiceCollectionExtensions.cs
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Loader; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyModel;using TianYa.DotNetShare.SharpCore.Model;namespace TianYa.DotNetShare.SharpCore.Extensions {/// <summary>/// ServiceCollection擴展/// </summary>public static class ServiceCollectionExtensions{#region 通過反射批量注入指定的程序集/// <summary>/// 通過反射批量注入指定的程序集/// </summary>/// <param name="services">服務</param>/// <param name="assemblyNames">程序集數組 如:["TianYa.DotNetShare.Repository","TianYa.DotNetShare.Service"],無需寫dll</param>public static void RegisterTianYaSharpService(this IServiceCollection services, params string[] assemblyNames){foreach (string assemblyName in assemblyNames){foreach (var itemClass in GetClassInterfacePairs(assemblyName)){foreach (var itemInterface in itemClass.Value){if (itemInterface != typeof(DataBaseSetting)){services.AddTransient(itemInterface, itemClass.Key); //DI依賴注入 }}}}}#endregion#region DI依賴注入輔助方法/// <summary>/// 獲取類以及類實現的接口鍵值對/// </summary>/// <param name="assemblyName">程序集名稱</param>/// <returns>類以及類實現的接口鍵值對</returns>private static Dictionary<Type, List<Type>> GetClassInterfacePairs(string assemblyName){//存儲 實現類 以及 對應接口Dictionary<Type, List<Type>> dic = new Dictionary<Type, List<Type>>();Assembly assembly = GetAssembly(assemblyName);if (assembly != null){Type[] types = assembly.GetTypes();foreach (var item in types.AsEnumerable().Where(x => !x.IsAbstract && !x.IsInterface && !x.IsGenericType)){dic.Add(item, item.GetInterfaces().Where(x => !x.IsGenericType).ToList());}}return dic;}/// <summary>/// 獲取所有的程序集/// </summary>/// <returns>程序集集合</returns>private static List<Assembly> GetAllAssemblies(){var list = new List<Assembly>();var deps = DependencyContext.Default;var libs = deps.CompileLibraries.Where(lib => !lib.Serviceable && lib.Type != "package");//排除所有的系統程序集、Nuget下載包foreach (var lib in libs){try{var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name));list.Add(assembly);}catch (Exception){// ignored }}return list;}/// <summary>/// 獲取指定的程序集/// </summary>/// <param name="assemblyName">程序集名稱</param>/// <returns>程序集</returns>private static Assembly GetAssembly(string assemblyName){return GetAllAssemblies().FirstOrDefault(assembly => assembly.FullName.Contains(assemblyName));}#endregion} }
并且添加一個Dynamic的擴展類DynamicExtensions.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Dynamic; using System.Linq; using System.Xml;namespace TianYa.DotNetShare.SharpCore.Extensions {/// <summary>/// Dynamic的擴展方法/// </summary>public static class DynamicExtensions{#region 匿名對象處理#region 將對象[主要是匿名對象]轉換為dynamic/// <summary>/// 將對象[主要是匿名對象]轉換為dynamic/// </summary>public static dynamic ToDynamic(this object value){IDictionary<string, object> expando = new ExpandoObject();var type = value.GetType();var properties = TypeDescriptor.GetProperties(type);foreach (PropertyDescriptor property in properties){var val = property.GetValue(value);if (property.PropertyType.FullName.StartsWith("<>f__AnonymousType")){dynamic dval = val.ToDynamic();expando.Add(property.Name, dval);}else{expando.Add(property.Name, val);}}return expando as ExpandoObject;}#endregion#region 將對象[主要是匿名對象]轉換為List<dynamic>/// <summary>/// 將對象[主要是匿名對象]轉換為List<dynamic>/// </summary>public static List<dynamic> ToDynamicList(this IEnumerable<dynamic> values){var list = new List<dynamic>();if (values != null){if (values.Any()){list.AddRange(values.Select(v => ((object)v).ToDynamic()));}}return list;}#endregion#region 將匿名對象集合轉換為XML/// <summary>/// 將匿名對象集合轉換為XML/// </summary>public static XmlDocument ListObjertToXML(this IEnumerable<dynamic> values){var xmlDoc = new XmlDocument();var xmlElem = xmlDoc.CreateElement("DocumentElement");xmlDoc.AppendChild(xmlElem);if (values != null){if (values.Any()){var node = xmlDoc.SelectSingleNode("DocumentElement");foreach (var item in values){var xmlRow = xmlDoc.CreateElement("Row");ObjectToXML(item, xmlDoc, xmlRow);node.AppendChild(xmlRow);}}}return xmlDoc;}#endregion#region 將匿名對象填充XML節點/// <summary>/// 將匿名對象填充XML節點/// </summary>private static void ObjectToXML(object value, XmlDocument xmlDoc, XmlElement xmlRow){IDictionary<string, object> expando = new ExpandoObject();var type = value.GetType();var properties = TypeDescriptor.GetProperties(type);foreach (PropertyDescriptor property in properties){var val = property.GetValue(value);xmlRow.CloneNode(false);var xmlTemp = xmlDoc.CreateElement(property.Name);XmlText xmlText;if (property.PropertyType.FullName.StartsWith("<>f__AnonymousType")){dynamic dval = val.ToDynamic();xmlText = xmlDoc.CreateTextNode(dval.ObjectToString());}else{xmlText = xmlDoc.CreateTextNode(val.ToString());}xmlTemp.AppendChild(xmlText);xmlRow.AppendChild(xmlTemp);}}#endregion#endregion} }
該擴展類主要在我們的Action向視圖傳遞匿名類型值的時候使用
六、Web層
本demo的web項目需要引用以下幾個程序集:
1、TianYa.DotNetShare.Model 我們的實體層
2、TianYa.DotNetShare.Service 我們的服務層
3、TianYa.DotNetShare.Repository 我們的倉儲層,正常我們的web項目是不應該使用倉儲層的,此處我們引用是為了演示IoC依賴注入
4、TianYa.DotNetShare.CommTool 我們的公共工具類庫
5、TianYa.DotNetShare.SharpCore 我們的Sharp核心類庫
到了這里我們所有的工作都已經準備好了,接下來就是開始做注入工作了。
打開我們的Startup.cs文件進行注入工作:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection;using TianYa.DotNetShare.SharpCore.Extensions;namespace TianYa.DotNetShare.CoreMvcDemo {public class Startup{public Startup(IConfiguration configuration){Configuration = configuration;}public IConfiguration Configuration { get; }// This method gets called by the runtime. Use this method to add services to the container.public void ConfigureServices(IServiceCollection services){services.Configure<CookiePolicyOptions>(options =>{// This lambda determines whether user consent for non-essential cookies is needed for a given request.options.CheckConsentNeeded = context => true;options.MinimumSameSitePolicy = SameSiteMode.None;});services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);//DI依賴注入,批量注入指定的程序集services.RegisterTianYaSharpService(new string[] { "TianYa.DotNetShare.Repository", "TianYa.DotNetShare.Service" });}// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.public void Configure(IApplicationBuilder app, IHostingEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}else{app.UseExceptionHandler("/Home/Error");}app.UseStaticFiles();app.UseCookiePolicy();app.UseMvc(routes =>{routes.MapRoute(name: "default",template: "{controller=Home}/{action=Index}/{id?}");});}} }
其中用來實現批量依賴注入的只要一句話就搞定了,如下所示:
//DI依賴注入,批量注入指定的程序集 services.RegisterTianYaSharpService(new string[] { "TianYa.DotNetShare.Repository", "TianYa.DotNetShare.Service" });
Sharp核心類庫在底層實現了批量注入的邏輯,程序集的注入必須按照先后順序進行,先進行倉儲層注入然后再進行服務層注入。
接下來我們來看看控制器里面怎么弄:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc;using TianYa.DotNetShare.CoreMvcDemo.Models; using TianYa.DotNetShare.Service; using TianYa.DotNetShare.Repository;namespace TianYa.DotNetShare.CoreMvcDemo.Controllers {public class HomeController : Controller{/// <summary>/// 定義倉儲層學生抽象類對象/// </summary>protected IStudentRepository StuRepository;/// <summary>/// 定義服務層學生抽象類對象/// </summary>protected IStudentService StuService;/// <summary>/// 通過構造函數進行注入/// 注意:參數是抽象類,而非實現類,因為已經在Startup.cs中將實現類映射給了抽象類/// </summary>/// <param name="stuRepository">倉儲層學生抽象類對象</param>/// <param name="stuService">服務層學生抽象類對象</param>public HomeController(IStudentRepository stuRepository, IStudentService stuService){this.StuRepository = stuRepository;this.StuService = stuService;}public IActionResult Index(){var stu1 = StuRepository.GetStuInfo("10000");var stu2 = StuService.GetStuInfo("10001");string msg = $"學號:10000,姓名:{stu1.Name},性別:{stu1.Sex},年齡:{stu1.Age}<br />";msg += $"學號:10001,姓名:{stu2.Name},性別:{stu2.Sex},年齡:{stu2.Age}";return Content(msg, "text/html", System.Text.Encoding.UTF8);}public IActionResult Privacy(){return View();}[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]public IActionResult Error(){return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });}} }
至此,完成處理,接下來就是見證奇跡的時刻了,我們來訪問一下/home/index,看看是否能返回學生信息。
我們可以發現,返回了學生的信息,說明我們注入成功了。
另外通過這個例子我們可以發現在注入倉儲層對象StudentRepository時,不僅控制器中注入成功了,而且在服務層中也注入成功了,說明我們ASP.NET Core內置的IoC容器DI依賴注入是全局的。
總結:
1、采用的是構造函數注入的方式,在構造函數中初始化賦值。
2、ASP.NET Core內置的IoC容器DI依賴注入是全局的。
3、DI批量依賴注入的核心思想就是根據程序集的名稱通過反射獲取類以及類實現的接口鍵值對字典,然后通過循環進行批量注入。
?
擴展:DI生命周期
生命周期是依賴注入設計原則里一個非常重要的概念,ASP.NET Core 一共有3種生命周期。
1、暫時(Transient):顧名思義,這種生命周期的對象是暫時的,每次請求都會創建一個新的實例。
services.AddTransient<IStudentRepository, StudentRepository>();
services.AddTransient<IStudentService, StudentService>();
2、作用域(Scoped):每次請求使用的是同一個實例。
services.AddScoped<IStudentRepository, StudentRepository>();
services.AddScoped<IStudentService, StudentService>();
3、單例(Singleton):第一次請求時就創建,以后每次請求都是使用相同的實例。
services.AddSingleton<IStudentRepository, StudentRepository>();
services.AddSingleton<IStudentService, StudentService>();
官方文檔建議:依賴注入是靜態/全局對象訪問模式的替代方法,如果將其與靜態對象訪問混合使用,則可能無法實現依賴關系注入的優點。
至此,本章就介紹完了,如果你覺得這篇文章對你有所幫助請記得點贊哦,謝謝!!!
demo源碼:
鏈接:https://pan.baidu.com/s/17GIgvp0JWy8BaNOE8l6p9A 提取碼:i9hh
?
參考博文:https://www.cnblogs.com/fei686868/p/11077997.html
版權聲明:如有雷同純屬巧合,如有侵權請及時聯系本人修改,謝謝!!!