.net core 依賴注入, autofac 簡單使用
- 2020 年 3 月 27 日
- 筆記
綜述
ASP.NET Core 支持依賴注入, 也推薦使用依賴注入. 主要作用是用來降低代碼之間的耦合度.
什麼是控制反轉?
控制反轉(Inversion of Control,縮寫為IoC),是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。
其中最常見的方式叫做”依賴注入”(Dependency Injection,簡稱DI),還有一種方式叫”依賴查找”(Dependency Lookup)
什麼是依賴注入?
這個概念分開來理解
1. 依賴, 當B類里要調用A類完成某個功能, 那麼就可以說是B依賴於A.
public class ClassA { public string FnA(string str) { return "A-" + str; } } public class ClassB { ClassA ca = new ClassA(); public string FnB(string str) { var art = ca.FnA(str); return "B-" + art; } }
上面這種方式是很常見的, 但是並不符合依賴的原則. 依賴的原則是: 依賴於抽象,而不是具體的實現. 也就是B類不能直接依賴A類, 應該依賴A的抽象接口.
2. 注入. 在這裡C類不去實例化A類,而是通過其他人傳遞給我,我只用就好。簡單來說就是別人對依賴創建實例化,我自己只負責使用,這個過程可以理解為注入.
public class ClassC { ClassA _ca; public ClassC(ClassA ca) { _ca = ca; } public string FnC(string str) { var art = _ca.FnA(str); return "C-" + art; } }
在上面的依賴中講過, 最好不要直接依賴實現,應該依賴抽象接口. 通過注入我們了解到, 我們不應該直接實例化依賴項,而應該別人創建, 我們只負責使用. 綜合一下, 我們就將原來的使用方式改為以下方式
/// <summary> /// 抽象接口A /// </summary> public interface InterfaceA { string FnA(string str); } /// <summary> /// 抽象接口A的具體實現 /// </summary> public class ClassA:InterfaceA { public string FnA(string str) { return "A-" + str; } } /// <summary> /// 傳統方式 /// </summary> public class ClassB { ClassA ca = new ClassA(); public string FnB(string str) { var art = ca.FnA(str); return "B-" + art; } } /// <summary> /// 依賴注入方式, 1.依賴於抽象接口,而不是具體實現; 2.依賴項不由我們自己創建,而只是負責使用 /// </summary> public class ClassD { readonly InterfaceA _ica; public ClassD(InterfaceA ica) { _ica = ica; } public string FnD(string str) { var art = _ica.FnA(str); return "D-" + art; } }
ClassB就是我們常見的使用方式, ClassD就是我們使用依賴注入的使用方式.
如何在.net core 里使用依賴注入?
1. 創建抽象接口(過程略)
2. 實現抽象接口(過程略)
3. 在Startup類的ConfigureServices方法中註冊服務
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddTransient<InterfaceA, ClassA>(); }
4. 在Controller中使用, 通過在構造函數中獲取實例, 然後使用
[ApiController] [Route("[controller]/[action]")] public class DemoController : ControllerBase { private readonly InterfaceA _interfaceA; /// <summary> /// 通過構造函數獲取實例 /// </summary> /// <param name="interfaceA"></param> public DemoController(InterfaceA interfaceA) { _interfaceA = interfaceA; } public IActionResult TestGet() { var rt = _interfaceA.FnA("uuu"); return Ok(rt); } }
結果
以上就是一個簡單的.net core 使用依賴注入的例子
如何選擇服務的生命周期?
在上面的第三步, 在註冊服務的時候我們使用的AddTransient , 除此之外還有 AddScoped,AddSingleton
AddTransient 暫時
暫時生存期服務 (AddTransient) 是每次從服務容器進行請求時創建的。 這種生存期適合輕量級、 無狀態的服務。
AddScoped 範圍內
作用域生存期服務 (AddScoped) 以每個客戶端請求(連接)一次的方式創建。
注意:在中間件內使用有作用域的服務時,請將該服務注入至 Invoke 或 InvokeAsync 方法。 請不要通過構造函數注入進行注入,因為它會強制服務的行為與單一實例類似。 有關詳細信息,請參閱 寫入自定義 ASP.NET Core 中間件。
AddSingleton 單例
單一實例生存期服務 (AddSingleton) 是在第一次請求時(或者在運行 Startup.ConfigureServices 並且使用服務註冊指定實例時)創建的。 每個後續請求都使用相同的實例。 如果應用需要單一實例行為,建議允許服務容器管理服務的生存期。 不要實現單一實例設計模式並提供用戶代碼來管理對象在類中的生存期。
注意:從單一實例解析有作用域的服務很危險。 當處理後續請求時,它可能會導致服務處於不正確的狀態。
什麼是容器?
講到這裡可能大家會有個疑問.
依賴注入把依賴的創建交給了別人, 我們只負責使用, 那麼這個誰創建, 在哪兒創建, 怎麼管理?
這個就引發了一個新的概念–容器. 容器就是負責關係的系統所有的依賴. 在我們.net core 有一個默認的容器, 專門用於管理這些依賴.
但是默認的容器在小型項目的時候夠了,但是大型項目就不夠了.
我們通過AddTransient這些方式去註冊服務, 那麼一個項目有非常的這種服務, 每一個都註冊,在startup這個裏面就變非常臃腫負責. 這個時候我們需要替換原來的容器.
如何更換默認容器?
在這裡我們介紹一種常用的第三方容器: AutoFac, .net core 版本使用的是3.1 其他版本會略有不同.
一. nuget安裝 Autofac.Extensions.DependencyInjection
二. 修改 Program.cs 文件
添加一句
.UseServiceProviderFactory(new Autofac.Extensions.DependencyInjection.AutofacServiceProviderFactory())//Autofac
三. 修改 Startup.cs
新增ConfigureContainer方法
public void ConfigureContainer(ContainerBuilder builder) { // 方式一 默認註冊 builder.RegisterType<ClassA>().As<InterfaceA>();//註冊 類似默認容器的services.AddTransient<InterfaceA, ClassA>(); }
上面這種方式是默認的註冊, 類似.net core 自帶的註冊. 但是並不合適我們批量註冊, 下面介紹一種掃描程序集的註冊方式
public void ConfigureContainer(ContainerBuilder builder) { //方式二 掃描程序集, RegisterAssemblyTypes接收包含一個或多個程序集的數組作為參數. 默認地, 程序中所有具體的類都將被註冊. var asm = Assembly.Load("Service");//指定dll名稱的程序集集 var defulatAsm = Assembly.GetExecutingAssembly();//默認執行的dll builder.RegisterAssemblyTypes(asm, defulatAsm) .PublicOnly()//僅註冊public的方法 .Except<Service.DemoService>()//排除某個類 .Where(t => t.Name.EndsWith("Service") || t.Name == "ClassA")//可以在這裡寫一些過濾類名規則 .AsImplementedInterfaces(); }
在controller里使用的方式和以前一樣
參考文獻