【C#】依赖注入及Autofac
文章目錄
- 什么是依賴?
- 什么是注入?
- 為什么要反轉?
- 何為容器
- 默認容器
- Autofac框架
- 框架使用
- 1、NuGet引入包
- 2、在ConfigureServices中創建容器對象,并返回IServiceProvider
 
- Autofac注冊組件
- 反射注冊
- 實例注冊
- Lambda表達式注冊
- 泛型注冊
- 條件注冊
- 屬性注入
- 程序集注冊
 
- Autofac暴露服務
- 默認暴露自身類型服務
- 多個暴露服務類型
 
- Autofac解析服務
- 程序集注冊指定暴露類型
- 解析服務
- 解析時傳參
 
- Autofac生命周期
- 暫時性
- 作用域內
- 匹配作用域內
 
- 來源
什么是依賴?
當一個類需要另一個類協作來完成工作的時候就產生了依賴。比如我們在AccountController這個控制器需要完成和用戶相關的注冊、登錄 等事情。其中的登錄我們由EF結合Idnetity來完成,所以我們封裝了一個EFLoginService。這里AccountController就有一個ILoginService的依賴。
什么是注入?
依賴注入(Dependency injection,DI)是一種實現對象及其合作者或依賴項之間松散耦合的技術。
注入體現的是一個IOC(控制反轉的的思想),把依賴的創建丟給其它人,自己只負責使用,其它人丟給你依賴的這個過程理解為注入。
private readonly IAccountAppService _accountAppService;/// <summary>/// 構造/// </summary>/// <param name="accountAppService">賬戶中心接口</param>public AccountController(IAccountAppService accountAppService){_accountAppService = accountAppService;}為什么要反轉?
為了在業務變化的時候盡少改動代碼可能造成的問題。
 比如我們現在要把從EF中去驗證登錄改為從Redis去讀,于是我們加了一個 RedisLoginService。這個時候我們只需要在原來注入的地方改一下就可以了。
何為容器
上面我們在使用AccountController的時候,我們自己通過代碼創建了一個IAccountServce的實例。想象一下,一個系統中如果有100個這樣的地方,我們是不是要在100個地方做這樣的事情? 控制是反轉了,依賴的創建也移交到了外部。現在的問題是依賴太多,我們需要一個地方統一管理系統中所有的依賴,容器誕生了。
 容器負責兩件事情:
- 綁定服務與實例之間的關系
- 獲取實例,并對實例進行管理(創建與銷毀)
默認容器
ASP.NET Core 的底層設計支持和使用依賴注入。ASP.NET Core
 應用程序可以利用內置的框架服務將它們注入到啟動類Startup的方法中,并且應用程序服務ConfigureServices能夠配置注入。
在內置的IoC容器中,在Startup.cs類文件ConfigureServices方法中,注入依賴方式如下:
 public void ConfigureServices(IServiceCollection services)
Autofac框架
AutoFac是一個開源的輕量級的依賴注入容器,也是.net下比較流行的實現依賴注入的工具之一。
將Autofac整合到你的應用的基本流程如下:
- 按照 控制反轉 (IoC) 的思想構建你的應用.
- 添加Autofac引用.
- 在應用的 startup 處
- 創建 ContainerBuilder.
- 注冊組件.
- 創建容器,將其保存以備后續使用.
- 應用執行階段
- 從容器中創建一個生命周期.
- 在此生命周期作用域內解析組件實例.
框架使用
1、NuGet引入包
Install-Package Autofac -Version 4.8.1 Install-Package
 Autofac.Extensions.DependencyInjection -Version 4.3.0
2、在ConfigureServices中創建容器對象,并返回IServiceProvider
一些需用到的方法:創建Runtime幫助獲取項目程序集,排除所有的系統程序集(Microsoft.***、System.***等)、Nuget下載包
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyModel; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Loader;namespace Dianshi.P2P.Core.Client.WebApi.Auth {[ApiExplorerSettings(IgnoreApi = true)]public class RuntimeHelper{/// <summary>/// 獲取項目程序集,排除所有的系統程序集(Microsoft.***、System.***等)、Nuget下載包/// </summary>/// <returns></returns>public static IList<Assembly> GetAllAssemblies(){List<Assembly> list = new List<Assembly>();var deps = DependencyContext.Default;//排除所有的系統程序集、Nuget下載包var libs = deps.CompileLibraries.Where(lib => !lib.Serviceable && lib.Type != "package");foreach (var lib in libs){try{var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name));list.Add(assembly);}catch (Exception ex){//}}return list;}public static Assembly GetAssembly(string assemblyName){return GetAllAssemblies().FirstOrDefault(f => f.FullName.Contains(assemblyName));}public static IList<Type> GetAllTypes(){List<Type> list = new List<Type>();foreach (var assembly in GetAllAssemblies()){var typeinfos = assembly.DefinedTypes;foreach (var typeinfo in typeinfos){list.Add(typeinfo.AsType());}}return list;}/// <summary>/// 根據AssemblyName獲取所有的類/// </summary>/// <param name="assemblyName"></param>/// <returns></returns>public static IList<Type> GetTypesByAssembly(string assemblyName){List<Type> list = new List<Type>();var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(assemblyName));var typeinfos = assembly.DefinedTypes;foreach (var typeinfo in typeinfos){list.Add(typeinfo.AsType());}return list;}public static Type GetImplementType(string typeName, Type baseInterfaceType){return GetAllTypes().FirstOrDefault(t =>{if (t.Name == typeName && t.GetTypeInfo().GetInterfaces().Any(b => b.Name == baseInterfaceType.Name)){var typeinfo = t.GetTypeInfo();return typeinfo.IsClass && !typeinfo.IsAbstract && !typeinfo.IsGenericType;}return false;});}} }然后
#region 依賴注入var builder = new ContainerBuilder();//實例化容器//注冊所有模塊modulebuilder.RegisterAssemblyModules(Assembly.GetExecutingAssembly());//獲取所有的程序集//var assemblys = BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToArray();var assemblys = RuntimeHelper.GetAllAssemblies().ToArray();//注冊所有繼承IDependency接口的類//builder.RegisterAssemblyTypes().Where(type => typeof(IDependency).IsAssignableFrom(type) && !type.IsAbstract);//注冊倉儲,所有IRepository接口到Repository的映射builder.RegisterAssemblyTypes(assemblys).Where(t => t.Name.EndsWith("Repository") && !t.Name.StartsWith("I")).AsImplementedInterfaces();//注冊服務,所有IApplicationService到ApplicationService的映射builder.RegisterAssemblyTypes(assemblys).Where(t => t.Name.EndsWith("AppService") && !t.Name.StartsWith("I")).AsImplementedInterfaces();builder.Populate(services);this.ApplicationContainer = builder.Build();return new AutofacServiceProvider(this.ApplicationContainer); //第三方IOC接管 core內置DI容器 //return services.BuilderInterceptableServiceProvider(builder => builder.SetDynamicProxyFactory());#endregionConfigureServices的返回值(默認為void)為IServiceProvider
測試
public ActionResult<string> Ioc(){//_accountAppService定義在《什么是注入?》章節下return _accountAppService.Login(new Application.Members.Accounts.Dto.LoginInput() { Password = "1", Phone = "2" });}Autofac注冊組件
通過創建 ContainerBuilder 來注冊組件,并且告訴容器哪些組件,暴露了哪些服務。
使用 Register() 方法來注冊實現:
ContainerBuilder 包含一組 Register() 注冊方法,而組件暴露服務,可用使用 ContainerBuilder 上的 As() 方法。
即在容器初始化時候,向容器組件添加對象的操作過程。
通過梳理Autofac所有可用的注冊組件方法,顯示如下圖展示的流程圖。
 
反射注冊
直接注冊的組件必須是具體的類型,并可用暴露抽象和接口作為服務,但不能注冊一個抽象和接口組件。
使用RegisterType()或者RegisterType(typeof(T))方法:
builder.RegisterType<TestService>().As<ITestService>(); // 或者 builder.RegisterType(typeof(TestService)).As(typeof(ITestService))在多個構造函數時,如果需要,也可手動指定一個構造函數。
使用 UsingConstructor 方法和構造方法中代表參數類型的類型。
builder.RegisterType<TestService>().UsingConstructor(typeof(TransientService), typeof(SingletonService));實例注冊
提前生成對象的實例并加入容器,以供注冊組件時使用。
使用RegisterInstance()方法
// new出一個對象注冊: var output = new StringWriter(); builder.RegisterInstance(output).As<TestService>();如果單例中存在實例且需要在容器中被組件使用時,
builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();Lambda表達式注冊
當組件創建不再是簡單調用構造方法時,可用利用lambda表達式來實現一些常規反射無法實現的操作。
比如一些復雜參數注冊,參數注入,以及選擇參數值實現等。
builder.Register(x => new TransientService()).As<ITransientService>(); // 或者指定參數builder.Register(x => new TestService(x.Resolve<ITransientService>(), x.Resolve<IScopedService>(), x.Resolve<ISingletonService>())).As<ITestService>().InstancePerLifetimeScope();泛型注冊
支持泛型注冊操作,使用 RegisterGeneric() 方法:
builder.RegisterGeneric(typeof(NHibernateRepository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();條件注冊
在一些特殊場景,可能需要通過加上判斷條件,來決定是否執行該條注冊語句。
兩種方法:
1、OnlyIf() - 提供一個表達式, 表示只有滿足條件,才會執行語句。
builder.RegisterType<Manager>().As<IManager>().OnlyIf(reg =>reg.IsRegistered(new TypedService(typeof(IService))) &®.IsRegistered(new TypedService(typeof(HandlerB))));2、IfNotRegistered() - 表示沒有其他服務注冊的情況下,就執行語句。
 方法在 ContainerBuilder.Build() 時執行并且以實際組件注冊的順序執行。
屬性注入
構造方法參數注入是一種傳值給組件的首選的方法。
在構造函數中是直接使用服務類型作為參數,然后AutoFac解析該類時,就會去容器內部已存在的組件中查找,然后將匹配的對象注入到構造函數中去。
但你同樣也可以使用屬性方法注入來傳值。
是將容器內對應的組件直接注入到類內的屬性中去,在注冊該屬性所屬類的時候,需要使用PropertiesAutowired()方法額外標注。
這里不討論屬性注入的好壞,也不做說明服務層屬性怎么注入,只討論說明控制器中屬性如何實現注入
1、注冊組件方法,并使用屬性注入PropertiesAutowired()標注。
builder.RegisterType<TransientService>().As<ITransientService>().PropertiesAutowired();2、在控制器中使用屬性來接收, 其中注入屬性必須標注為public
[ApiController] [Route("[controller]")] public class TestController : ControllerBase {public ITransientService _transientService { get; set; }[HttpGet]public JsonResult Get(){var data1 = _transientService.GetGuid();return new JsonResult(new {data1});} }程序集注冊
當我們需要實現批量注冊的時候,也可以使用程序集的方式來注冊,這也是常用的方法。
可通過指定過濾類型,服務,掃描模塊等方式來找到需要注冊的組件。
var assemblies = Assembly.GetExecutingAssembly();builder.RegisterAssemblyTypes(assemblies)//程序集內所有具象類 .Where(c => c.Name.EndsWith("Service")) .PublicOnly()//只要public訪問權限的 .Where(cc => cc.IsClass)//只要class型(主要為了排除值和interface類型) .AsImplementedInterfaces();//自動以其實現的所有接口類型暴露(包括IDisposable接口)說明:
- RegisterAssemblyTypes() :接收包含一個或多個程序集的數組作為參數
- RegisterAssemblyModules() : 接收模塊作為參數,進行模塊掃描注冊
- PublicOnly() :指定公有方法被注冊
- Where() :要過濾注冊的類型
- Except() :要排除的類型
- As() :反射出其實現的接口
- AsImplementedInterfaces() : 自動以其實現的所有接口類型暴露(包括IDisposable接口)
Autofac暴露服務
上面提到了注冊組件時, 我們得告訴Autofac, 組件暴露了哪些服務。
在上面注冊實現中,大部分使用到了As() 方法。
當然,Autofac也提供了其他標注來暴露服務的方法。
默認暴露自身類型服務
常用的幾種方法如下:
builder.RegisterType<CallLogger>();//不標注,默認以自身類型暴露服務 builder.RegisterType<CallLogger>().AsSelf(); builder.RegisterType<CallLogger>().As<CallLogger>(); builder.RegisterType<CallLogger>().As(typeof(CallLogger));多個暴露服務類型
以其實現的接口(interface)暴露服務,暴露的類型可以是多個,比如CallLogger類實現了ILogger接口和ICallInterceptor接口。
暴露服務后, 可以解析基于該服務的組件了. 但請注意, 一旦將組件暴露為一個特定的服務, 默認的服務 (組件類型) 將被覆蓋。
所以,為了防止被其他服務覆蓋,可以使用 AsSelf() 方法。
Autofac解析服務
Copybuilder.RegisterType<CallLogger>().As<ILogger>().As<ICallInterceptor>().AsSelf();這樣你既可以實現組件暴露一系列特定的服務, 又可以讓它暴露默認的服務。
程序集注冊指定暴露類型
1、可通過指定接口類型暴露服務,使用As() 方法
publi void ConfigureContainer(ContainerBuilder builder) {builder.RegisterAssemblyTypes(assemblies)//程序集內所有具象類.Where(cc =>cc.Name.EndsWith("Repository")|//篩選cc.Name.EndsWith("Service")).As(x=>x.GetInterfaces()[0])//反射出其實現的接口,并指定以其實現的第一個接口類型暴露 }2、指定所有實現的接口類型進行暴露
使用AsImplementedInterfaces()函數實現,相當于一個類實現了幾個接口(interface)就會暴露出幾個服務,等價于上面連寫多個As()的作用。
publi void ConfigureContainer(ContainerBuilder builder) { builder.RegisterAssemblyTypes(asm).Where(t => t.Name.EndsWith("Repository")).AsImplementedInterfaces();//自動以其實現的所有接口類型暴露(包括IDisposable接口) }解析服務
在注冊完組件并暴露相應的服務后, 可以從創建的容器或其生命周期中解析服務。
使用 Resolve() 方法來解析實現:
通過梳理Autofac所有可用的解析服務方法,顯示如下圖展示的流程圖。
 
 在 注冊完組件并暴露相應的服務后, 你可以從創建的容器或其子 生命周期 中解析服務. 讓我們使用 Resolve() 方法來實現:
解析時傳參
當解析服務時, 需要傳參,可以使用Resolve() 方法來接受可變長度的參數。
- 可用參數類型
NamedParameter - 通過名稱匹配目標參數
TypedParameter - 通過類型匹配目標參數 (需要匹配具體類型)
ResolvedParameter - 靈活的參數匹配
- 反射組件的參數
- Lambda表達式組件的參數
- 不顯式調用Resolve傳參
Autofac生命周期
 下面講下AutoFac定義的幾種生命周期作用域,并與.NET Core默認的生命周期作了簡要的對比。
暫時性
每次在向服務容器進行請求時都會創建新的實例,相當于每次都new出一個。
注冊方式:
使用InstancePerDependency()方法標注,如果不標注,這也是默認的選項。以下兩種注冊方法是等效的:
//不指定,默認就是瞬時的 builder.RegisterType<TransientService>().As<ITransientService>();//指定其生命周期域為瞬時 builder.RegisterType<TransientService>().As<ITransientService>().InstancePerDependency();對比:
與默認的容器中自帶的生命周期AddTransient相同,也是每次都是全新的實例。
 使用AddTransient()注冊:
作用域內
在每次Web請求時被創建一次實例,生命周期橫貫整次請求。即在每個生命周期作用域內是單例的。
注冊方式:
使用InstancePerLifetimeScope()方法標識:
builder.RegisterType<ScopedService>().As<IScopedService>().InstancePerLifetimeScope();對比:
與默認的容器中自帶的生命周期AddScoped相同,.NET Core框架自帶的容器全權接管了請求和生命周期作用域的創建,使用Scoped()可以實現相同的效果。
 使用AddScoped()注冊:
匹配作用域內
即每個匹配的生命周期作用域一個實例。
 該類型其實是上面的“作用域內”的其中一種,可以對實例的共享有更加精準的控制.。我們通過允許給域“打標簽”,只要在這個特定的標簽域內就是單例的。
- 注冊
使用InstancePerMatchingLifetimeScope(string tagName)方法注冊:
var builder = new ContainerBuilder(); builder.RegisterType<Worker>().InstancePerMatchingLifetimeScope("myrequest");當你開始一個生命周期時, 提供的標簽值和它就關聯起來了。
- 解析
對比:
services.AddSingleton<IProductService, ProductService>();來源
.NET Core API框架實戰(五) 依賴注入 服務的注冊與提供
 
 Autofac 框架初識與應用
總結
以上是生活随笔為你收集整理的【C#】依赖注入及Autofac的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Autofac 快速入门
- 下一篇: 【腾讯Bugly干货分享】JSPatch
