原本計劃是五篇文章的,每章發個半小時隨便翻翻就能懂,可是第一篇發了以後,我發現.NET環境下不少人對IoC和DI都很排斥,搞得評論區異常熱鬧。git
同一個東西,在Java下和在.NET下能有這麼大的差別,也是挺有意思的一件事情。github
因此我就把剩下四篇內容精簡再精簡,合成一篇了,權當是寫給本身的一個備忘記錄了。web
GitHub源碼地址:https://github.com/WangRui321/Ray.EssayNotes.AutoFac數據庫
源碼是一個虛構的項目框架,相似於樣例性質的代碼或者測試程序,裏面不少註釋,對理解DI,或怎麼在MVC、WebApi和Core Api分別實現依賴注入有很好的幫助效果。
因此,如下內容,配合源碼食用效果更佳~api
老規矩,理論先行。mvc
一串聲明瞭它所提供服務和它所消費依賴的代碼。app
能夠理解爲容器內的基本單元,一個容器內會被註冊不少個組件,每一個組件都有本身的信息:好比暴露的服務類型、生命週期域、綁定的具象對象等。框架
一個在提供和消費組件之間明肯定義的行爲約定。
和項目中的xxxService不一樣,AutoFac的服務是對容器而言的,能夠簡單的理解爲上一章講的組件的暴露類型
(即對外開放的服務類型),也就是As方法裏的東西:
builder.RegisterType<CallLogger>() .As<ILogger>() .As<ICallInterceptor>();
這裏,針對同一個註冊對象(CallLogger),容器就對外暴露了兩個服務(service),ILogger服務和ICallInterceptor服務。
指服務實例在你的應用中存在的時長:從開始實例化到最後釋放結束。
指它在應用中能共享給其餘組件並被消費的做用域。例如, 應用中有個全局的靜態單例,那麼該全局對象實例的 "做用域" 將會是整個應用。
實際上是把這兩個概念組合在了一塊兒, 能夠理解爲應用中的一個工做單元。後面詳細講。
容器是一個自動售貨機
,組件是放在裏面的在售商品
,服務是商品的出售名稱
。
把商品(項目裏的具象對象)放入自動售貨機(容器)上架的過程叫註冊
;
註冊的時候會給商品貼上標籤,標註該商品的名稱,這個名稱就叫服務
;
咱們還能夠標註這個商品的適用人羣和過時時間等(生命週期做用域
);
把這個包裝後的商品放入自動售貨機後,它就變成了在售商品(組件
)。
當有顧客須要某個商品時,他只要對着售貨機報一個商品名(服務
名),自動售貨機找到對應商品,拋出給客戶,這個拋給你的過程,就叫作注入
你;
並且這個售貨機比較智能,拋出前還能夠先判斷商品是否是過時了,該不應拋給你。
即在容器初始化時,向容器內添加對象的操做。AutoFac封裝瞭如下幾種便捷的註冊方法:
直接指定注入對象與暴露類型,使用RegisterType<T>()
或者RegisterType(typeof(T))
方法:
builder.RegisterType<StudentRepository>() .As<IStudentRepository>(); builder.RegisterType(typeof(StudentService)) .As(typeof(IStudentService));
將實例註冊到容器,使用RegisterInstance()
方法,一般有兩種:
var output = new StringWriter(); builder.RegisterInstance(output).As<TextWriter>();
builder.RegisterInstance(MySingleton.Instance).ExternallyOwned();
builder.Register(x => new StudentRepository()) .As<IStudentRepository>(); builder.Register(x => new StudentService(x.Resolve<IStudentRepository>())) .As<IStudentService>();
利用拉姆達註冊能夠實現一些常規反射沒法實現的操做,好比一些複雜參數註冊。
最多見的就是泛型倉儲的註冊:
builder.RegisterGeneric(typeof(BaseRepository<>)) .As(typeof(IBaseRepository<>)) .InstancePerLifetimeScope();
經過加上判斷條件,來決定是否執行該條註冊語句。
表示:若是沒註冊過xxx,就執行語句:
builder.RegisterType<TeacherRepository>() .AsSelf() .IfNotRegistered(typeof(ITeacherRepository));
只有當ITeacherRepository服務類型沒有被註冊過,纔會執行該條註冊語句。
表示:只有...,纔會執行語句:
builder.RegisterType<TeacherService>() .AsSelf() .As<ITeacherService>() .OnlyIf(x => x.IsRegistered(new TypedService(typeof(ITeacherRepository)))|| x.IsRegistered(new TypedService(typeof(TeacherRepository))));
只有當ITeacherRepository服務類型或者TeacherRepository服務類型被註冊過,纔會執行該條註冊語句。
最經常使用,也最實用的一個註冊方法,使用該方法最好要懂點反射
的知識。
/// <summary> /// 經過反射程序集批量註冊 /// </summary> /// <param name="builder"></param> public static void BuildContainerFunc8(ContainerBuilder builder) { Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssemblies(); builder.RegisterAssemblyTypes(assemblies)//程序集內全部具象類(concrete classes) .Where(cc =>cc.Name.EndsWith("Repository")|//篩選 cc.Name.EndsWith("Service")) .PublicOnly()//只要public訪問權限的 .Where(cc=>cc.IsClass)//只要class型(主要爲了排除值和interface類型) //.Except<TeacherRepository>()//排除某類型 //.As(x=>x.GetInterfaces()[0])//反射出其實現的接口,默認以第一個接口類型暴露 .AsImplementedInterfaces();//自動以其實現的全部接口類型暴露(包括IDisposable接口) builder.RegisterGeneric(typeof(BaseRepository<>)) .As(typeof(IBaseRepository<>)); }
如上會批量註冊項目中全部的Repository和Service。
講屬性注入以前,要先看下構造注入。
/// <summary> /// 學生邏輯處理 /// </summary> public class StudentService : IStudentService { private readonly IStudentRepository _studentRepository; /// <summary> /// 構造注入 /// </summary> /// <param name="studentRepository"></param> public StudentService(IStudentRepository studentRepository) { _studentRepository = studentRepository; } }
在構造函數的參數中直接寫入服務類型,AutoFac解析該類時,就會去容器內部已存在的組件中查找,而後將匹配的對象注入到構造函數中去。
/// <summary> /// 教師邏輯處理 /// </summary> public class TeacherService : ITeacherService { /// <summary> /// 用於屬性注入 /// </summary> public ITeacherRepository TeacherRepository { get; set; } public string GetTeacherName(long id) { return TeacherRepository?.Get(111).Name; } }
要使用這種屬性注入,在註冊該屬性所屬類的時候,須要使用PropertiesAutowired()
方法額外標註,以下:
builder.RegisterType<TeacherService>().PropertiesAutowired();
這樣,容器在解析並實例化TeacherService類時,便會將容器內的組件與類內的屬性作映射,若是相同則自動將組件注入到類內屬性種。
屬性注入爭議性很大,不少人稱這是一種_反模式_,事實也確實如此。
使用屬性注入會讓代碼可讀性變得極其複雜(而複雜難懂的代碼必定不是好的代碼,無論用的技術有多高大上)。
可是屬性注入也不是一無可取,由於屬性注入有一個特性:
在構造注入的時候,若是構造函數的參數中有一個對象在容器不存在,那麼解析就會報錯。
可是屬性注入就不同了,當容器內沒有與該屬性類型對應的組件時,這時解析不會報異常,只會讓這個屬性保持爲空類型(null)。
利用這個特性,能夠實現一些特殊的操做。
即上面提到的As<xxx>()
函數,AutoFac提供瞭如下三種標註暴露服務類型的方法:
使用AsSelf()
方法標識,表示以其自身類型暴露,也是當沒有標註暴露服務的時候的默認選項。
以下四種寫法是等效的:
builder.RegisterType<StudentService>();//不標註,默認以自身類型暴露服務 builder.RegisterType<StudentService>().AsSelf(); builder.RegisterType<StudentService>().As<StudentService>(); builder.RegisterType<StudentService>().As(typeof(StudentService));
使用As()
方法標識,暴露的類型能夠是多個,好比CallLogger類實現了ILogger接口和ICallInterceptor接口,那麼能夠這麼寫:
builder.RegisterType<CallLogger>() .As<ILogger>() .As<ICallInterceptor>() .AsSelf();
public static void BuildContainerFunc8(ContainerBuilder builder) { Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssemblies(); builder.RegisterAssemblyTypes(assemblies)//程序集內全部具象類(concrete classes) .Where(cc =>cc.Name.EndsWith("Repository")|//篩選 cc.Name.EndsWith("Service")) .As(x=>x.GetInterfaces()[0])//反射出其實現的接口,並指定以其實現的第一個接口類型暴露 }
使用AsImplementedInterfaces()
函數實現,至關於一個類實現了幾個接口(interface)就會暴露出幾個服務,等價於上面連寫多個As()的做用。
public static void BuildContainerFunc8(ContainerBuilder builder) { Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssemblies(); builder.RegisterAssemblyTypes(assemblies)//程序集內全部具象類(concrete classes) .Where(cc =>cc.Name.EndsWith("Repository")|//篩選 cc.Name.EndsWith("Service")) .AsImplementedInterfaces();//自動以其實現的全部接口類型暴露(包括IDisposable接口) }
至關於UnitWork(工做單元)的概念。下面羅列出了AutoFac與.NET Core的生命週期做用域,並做了簡要的對比。
下面講下AutoFac定義的幾種生命週期做用域,上一篇評論裏也有人提了,關於生命週期做用域這塊確實不是很好理解,因此下面每中類型我都寫了一個例子程序,這些例子程序對理解頗有幫助,只要能讀懂這些例子程序,就必定能弄懂這些生命週期做用域。(例子項目源碼裏都有,能夠去試着實際運行下,更易理解)
也叫每一個依賴一個實例。
即每次從容器裏拿出來的都是全新對象,至關於每次都new出一個。
在其餘容器中也被標識爲 'Transient'(瞬時) 或 'Factory'(工廠)。
使用InstancePerDependency()
方法標註,若是不標註,這也是默認的選項。如下兩種註冊方法是等效的:
//不指定,默認就是瞬時的 builder.RegisterType<Model.StudentEntity>(); //指定其生命週期域爲瞬時 builder.RegisterType<Model.StudentEntity>().InstancePerDependency();
using (var scope = Container.Instance.BeginLifetimeScope()) { var stu1 = scope.Resolve<Model.StudentEntity>(); Console.WriteLine($"第1次打印:{stu1.Name}"); stu1.Name = "張三"; Console.WriteLine($"第2次打印:{stu1.Name}"); var stu2 = scope.Resolve<Model.StudentEntity>(); Console.WriteLine($"第2次打印:{stu2.Name}"); }
上面解析了2次,有兩個實例,stu1和stu2指向不一樣的兩塊內存,彼此之間沒有關係。
打印結果:
即全局只有一個實例,在根容器和全部嵌套做用域內,每次解析返回的都是同一個實例。
使用SingleInstance()
方法標識:
builder.RegisterType<Model.StudentEntity>().SingleInstance();
//直接從根域內解析(單例下可使用,其餘不建議這樣直接從根域內解析) var stu1 = Container.Instance.Resolve<Model.StudentEntity>(); stu1.Name = "張三"; Console.WriteLine($"第1次打印:{stu1.Name}"); using (var scope1 = Container.Instance.BeginLifetimeScope()) { var stu2 = scope1.Resolve<Model.StudentEntity>(); Console.WriteLine($"第2次打印:{stu2.Name}"); stu1.Name = "李四"; } using (var scope2 = Container.Instance.BeginLifetimeScope()) { var stu3 = scope2.Resolve<Model.StudentEntity>(); Console.WriteLine($"第3次打印:{stu3.Name}"); }
上面的stu一、stu二、stu3都是同一個實例,在內存上它們指向同一個內存塊。
打印結果:
即在每一個生命週期域內是單例的。
InstancePerLifetimeScope()
方法標識:x.RegisterType<Model.StudentEntity>().InstancePerLifetimeScope();
//子域一 using (var scope1 = Container.Instance.BeginLifetimeScope()) { var stu1 = scope1.Resolve<Model.StudentEntity>(); Console.WriteLine($"第1次打印:{stu1.Name}"); stu1.Name = "張三"; var stu2 = scope1.Resolve<Model.StudentEntity>(); Console.WriteLine($"第2次打印:{stu2.Name}"); } //子域二 using (var scope2 = Container.Instance.BeginLifetimeScope()) { var stuA = scope2.Resolve<Model.StudentEntity>(); Console.WriteLine($"第3次打印:{stuA.Name}"); stuA.Name = "李四"; var stuB = scope2.Resolve<Model.StudentEntity>(); Console.WriteLine($"第4次打印:{stuB.Name}"); }
如上,在子域一中,雖然解析了2次,可是2次解析出的都是同一個實例,即stu1和stu2指向同一個內存塊Ⅰ。
子域二也同樣,stuA和stuB指向同一個內存塊Ⅱ,可是內存塊Ⅰ和內存塊Ⅱ卻不是同一塊。
打印結果以下,第1次和第3次爲null:
即每一個匹配的
生命週期做用域一個實例。
該域類型實際上是上面的「域內單例」的其中一種,不同的是它容許咱們給域「打標籤」,只要在這個特定的標籤域內就是單例的。
InstancePerMatchingLifetimeScope(string tagName)
方法註冊:builder.RegisterType<Worker>().InstancePerMatchingLifetimeScope("myTag");
//myScope標籤子域一 using (var myScope1 = Container.Instance.BeginLifetimeScope("myTag")) { var stu1 = myScope1.Resolve<Model.StudentEntity>(); stu1.Name = "張三"; Console.WriteLine($"第1次打印:{stu1.Name}"); var stu2 = myScope1.Resolve<Model.StudentEntity>(); Console.WriteLine($"第2次打印:{stu2.Name}"); //解析了2次,但2次都是同一個實例(stu1和stu2指向同一個內存塊Ⅰ) } //myScope標籤子域二 using (var myScope2 = Container.Instance.BeginLifetimeScope("myTag")) { var stuA = myScope2.Resolve<Model.StudentEntity>(); Console.WriteLine($"第3次打印:{stuA.Name}"); //由於標籤域內已註冊過,因此能夠解析成功 //可是由於和上面不是同一個子域,因此解析出的實例stuA與以前的並非同一個實例,指向另外一個內存塊Ⅱ } //無標籤子域三 using (var noTagScope = Container.Instance.BeginLifetimeScope()) { try { var stuOne = noTagScope.Resolve<Model.StudentEntity>();//會報異常 Console.WriteLine($"第4次正常打印:{stuOne.Name}"); } catch (Exception e) { Console.WriteLine($"第4次異常打印:{e.Message}"); } //由於StudentEntity只被註冊到帶有myScope標籤域內,因此這裏解析不到,報異常 }
打印結果:
須要注意:
該種類型適用於「request」類型的應用,好比MVC和WebApi。
其實質其實又是上一種的「指定域內單例」的一種特殊狀況:AutoFac內有一個靜態字符串叫Autofac.Core.Lifetime.MatchingScopeLifetimeTags.RequestLifetimeScopeTag
,其值爲"AutofacWebRequest"
,當「指定域內單例」打的標籤是這個字符串時,那它就是「每次請求內單例」了。
InstancePerRequest()
方法標註:builder.RegisterType<Model.StudentEntity>().InstancePerRequest();
也可使用上面的域內單例的註冊法(可是不建議):
//使用靜態字符串標記 builder.RegisterType<Model.StudentEntity>().InstancePerMatchingLifetimeScope(Autofac.Core.Lifetime.MatchingScopeLifetimeTags.RequestLifetimeScopeTag); //或者直接寫明字符串 builder.RegisterType<Model.StudentEntity>().InstancePerMatchingLifetimeScope("AutofacWebRequest");
這裏用控制檯程序很差舉例子就不寫解析代碼了,要理解「每次請求內單例」的做用,最好的例子就是EF中的DBContext,咱們在一次request請求內,即便是用到了多個Service和多個Repository,也只須要一個數據庫實例,這樣即能減小數據庫實例初始化的消耗,還能實現事務的功能。
相比於AutoFac的豐富複雜,.NET Core就比較簡單粗暴了,只要3種類型:
與AutoFac的瞬時實例(Instance Per Dependency)相同,每次都是全新的實例。
使用AddTransient()
註冊:
services.AddTransient<IStudentService, StudentService>();
其意義與AutoFac的請求內單例(Instance Per Request)相同,但實際若是真正在.NET Core中使用使用AutoFac的話,應該使用AutoFac的域內單例(Instance Per LifetimeScope)來代替。
緣由是.NET Core框架自帶的DI(Microsoft.Extensions.DependencyInjection
)全權接管了請求和生命週期做用域的建立,因此AutoFac沒法控制,可是使用域內單例(Instance Per LifetimeScope)能夠實現相同的效果。
使用AddScoped()
註冊:
services.AddScoped<IStudentService, StudentService>();
與AutoFac的單例(Single Instance)相同。
使用AddSingleton();
註冊:
services.AddSingleton<StudentEntity>();
思路很簡單,三步走:
新建AutoFac容器
初始化容器,向容器註冊全部須要的依賴對象
將AutoFac解析器設置爲系統的依賴解析器(Dependency Resolver)
除了AutoFac主包以外,還須要Nuget導入AutoFac.Mvc5包:
容器代碼:
using System; using System.Linq; using System.Reflection; // using Autofac; using Autofac.Integration.Mvc; // using Ray.EssayNotes.AutoFac.Repository.IRepository; using Ray.EssayNotes.AutoFac.Repository.Repository; namespace Ray.EssayNotes.AutoFac.Infrastructure.Ioc { /// <summary> /// .net framework MVC程序容器 /// </summary> public static class MvcContainer { public static IContainer Instance; /// <summary> /// 初始化MVC容器 /// </summary> /// <param name="func"></param> /// <returns></returns> public static System.Web.Mvc.IDependencyResolver Init(Func<ContainerBuilder, ContainerBuilder> func = null) { //新建容器構建器,用於註冊組件和服務 var builder = new ContainerBuilder(); //註冊組件 MyBuild(builder); func?.Invoke(builder); //利用構建器建立容器 Instance = builder.Build(); //返回針對MVC的AutoFac解析器 return new AutofacDependencyResolver(Instance); } public static void MyBuild(ContainerBuilder builder) { Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssembliesWeb(); //註冊倉儲 && Service builder.RegisterAssemblyTypes(assemblies)//程序集內全部具象類(concrete classes) .Where(cc => cc.Name.EndsWith("Repository") |//篩選 cc.Name.EndsWith("Service")) .PublicOnly()//只要public訪問權限的 .Where(cc => cc.IsClass)//只要class型(主要爲了排除值和interface類型) .AsImplementedInterfaces();//自動以其實現的全部接口類型暴露(包括IDisposable接口) //註冊泛型倉儲 builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)); //註冊Controller //方法1:本身根據反射註冊 //builder.RegisterAssemblyTypes(assemblies) // .Where(cc => cc.Name.EndsWith("Controller")) // .AsSelf(); //方法2:用AutoFac提供的專門用於註冊MvcController的擴展方法 Assembly mvcAssembly = assemblies.FirstOrDefault(x => x.FullName.Contains(".NetFrameworkMvc")); builder.RegisterControllers(mvcAssembly); } } }
這裏Init()
初始化函數返回類型變成了System.Web.Mvc.IDependencyResolver
接口,即MVC的系統依賴解析器。
AutoFac本身封裝了一個AutofacDependencyResolver
類(AutoFac依賴解析器類)實現了這個接口,因此直接new一個AutofacDependencyResolver類返回,等下把這個AutoFac依賴解析器類設置爲MVC的系統依賴解析器就能夠了。
using System; using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Web.Mvc; namespace Autofac.Integration.Mvc { /// <summary> /// Autofac implementation of the <see cref="T:System.Web.Mvc.IDependencyResolver" /> interface. /// </summary> public class AutofacDependencyResolver : IDependencyResolver { //內部實現 //...... }
啓動時初始化容器,並把AutoFac生成的解析器設置爲系統依賴解析器:
using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; // using Ray.EssayNotes.AutoFac.Infrastructure.Ioc; namespace Ray.EssayNotes.AutoFac.NetFrameworkMvc { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); //初始化容器,並返回適用於MVC的AutoFac解析器 System.Web.Mvc.IDependencyResolver autoFacResolver = MvcContainer.Init(); //將AutoFac解析器設置爲系統DI解析器 DependencyResolver.SetResolver(autoFacResolver); } } }
其中DependencyResolver.SetResolver()
爲MVC封裝的一個靜態方法,用於設置MVC的依賴解析器。
其參數只要是實現了System.Web.Mvc.IDependencyResolver
接口的對象均可以,AutoFac本身封裝的解析器AutofacDependencyResolver
類實現了這個接口,因此能夠傳進來,從而實現了讓AutoFac接管MVC的依賴注入。
直接利用構造注入就能夠了:
using System.Web.Mvc; // using Ray.EssayNotes.AutoFac.Service.IService; namespace Ray.EssayNotes.AutoFac.NetFrameworkMvc.Controllers { /// <summary> /// 學生Api /// </summary> public class StudentController : Controller { private readonly IStudentService _studentService; /// <summary> /// 構造注入 /// </summary> /// <param name="studentService"></param> public StudentController(IStudentService studentService) { _studentService = studentService; } /// <summary> /// 獲取學生姓名 /// </summary> /// <param name="id"></param> /// <returns></returns> public string GetStuNameById(long id) { return _studentService.GetStuName(id); } } }
和MVC同樣,思路很簡單,三步走:
新建AutoFac容器
初始化容器,向容器註冊全部須要的依賴對象
將AutoFac解析器設置爲系統的依賴解析器(Dependency Resolver)
除了AutoFac主包以外,還須要Nuget導入AutoFac.WebApi2包:
容器代碼:
using System; using System.Linq; using System.Reflection; // using Autofac; using Autofac.Integration.WebApi; // using Ray.EssayNotes.AutoFac.Repository.Repository; using Ray.EssayNotes.AutoFac.Repository.IRepository; namespace Ray.EssayNotes.AutoFac.Infrastructure.Ioc { /// <summary> /// .NET Framework WebApi容器 /// </summary> public static class ApiContainer { public static IContainer Instance; /// <summary> /// 初始化Api容器 /// </summary> /// <param name="func"></param> public static System.Web.Http.Dependencies.IDependencyResolver Init(Func<ContainerBuilder, ContainerBuilder> func = null) { //新建容器構建器,用於註冊組件和服務 var builder = new ContainerBuilder(); //註冊組件 MyBuild(builder); func?.Invoke(builder); //利用構建器建立容器 Instance = builder.Build(); //返回針對WebApi的AutoFac解析器 return new AutofacWebApiDependencyResolver(Instance); } public static void MyBuild(ContainerBuilder builder) { var assemblies = Helpers.ReflectionHelper.GetAllAssembliesWeb(); //註冊倉儲 && Service builder.RegisterAssemblyTypes(assemblies)//程序集內全部具象類(concrete classes) .Where(cc => cc.Name.EndsWith("Repository") |//篩選 cc.Name.EndsWith("Service")) .PublicOnly()//只要public訪問權限的 .Where(cc => cc.IsClass)//只要class型(主要爲了排除值和interface類型) .AsImplementedInterfaces();//自動以其實現的全部接口類型暴露(包括IDisposable接口) //註冊泛型倉儲 builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)); //註冊ApiController //方法1:本身根據反射註冊 //Assembly[] controllerAssemblies = assemblies.Where(x => x.FullName.Contains(".NetFrameworkApi")).ToArray(); //builder.RegisterAssemblyTypes(controllerAssemblies) // .Where(cc => cc.Name.EndsWith("Controller")) // .AsSelf(); //方法2:用AutoFac提供的專門用於註冊ApiController的擴展方法 Assembly mvcAssembly = assemblies.FirstOrDefault(x => x.FullName.Contains(".NetFrameworkApi")); builder.RegisterApiControllers(mvcAssembly); } } }
這裏Init()
初始化函數返回類型變成了System.Web.Http.Dependencies.IDependencyResolver
接口,即WebApi的系統依賴解析器。
AutoFac本身封裝了一個AutofacWebApiDependencyResolver
類(AutoFac針對WebApi的依賴解析器類)實現了這個接口,因此直接new一個AutofacWebApiDependencyResolver類返回,等下把這個AutoFac依賴解析器類設置爲WebApi的系統依賴解析器就能夠了。
在項目啓動時初始化容器:
using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; // using Ray.EssayNotes.AutoFac.Infrastructure.Ioc; namespace Ray.EssayNotes.AutoFac.NetFrameworkApi { public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); GlobalConfiguration.Configure(WebApiConfig.Register); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); //初始化容器,並返回適用於WebApi的AutoFac解析器 System.Web.Http.Dependencies.IDependencyResolver autoFacResolver = ApiContainer.Init(); //獲取HttpConfiguration HttpConfiguration config = GlobalConfiguration.Configuration; //將AutoFac解析器設置爲系統DI解析器 config.DependencyResolver = autoFacResolver; } } }
這裏跟上面的MVC項目不太同樣,是經過HttpConfiguration對象來設置依賴解析器的,可是原理相同,不贅述了。
直接利用構造函數注入便可:
using System.Web.Http; // using Ray.EssayNotes.AutoFac.Service.IService; namespace Ray.EssayNotes.AutoFac.NetFrameworkApi.Controllers { /// <summary> /// 學生Api /// </summary> public class StudentController : ApiController { private readonly IStudentService _studentService; /// <summary> /// 構造注入 /// </summary> /// <param name="studentService"></param> public StudentController(IStudentService studentService) { _studentService = studentService; } /// <summary> /// 獲取學生姓名 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet] [Route("Student/GetStuNameById")] public string GetStuNameById(long id) { return _studentService.GetStuName(123); } } }
與.NET Framework不一樣,.NET Core把DI提到了很是重要的位置,其框架自己就集成了一套DI容器。
針對其自帶DI,主要理解兩個對象,IServiceCollection和 IServiceProvider。
用於向容器註冊服務,能夠和AutoFac的ContainerBuilder(容器構建器)類比。
負責從容器中向外部提供實例,能夠和AutoFac的解析的概念類比。
註冊的地方就在主程序下的startup類中。
可是其自己的註冊語法並無AutoFac那麼豐富,泛型註冊、批量註冊這些全都沒有,只有下面這種最基礎的一個一個註冊的形式:
using System.Linq; using System.Reflection; // using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; // using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Extensions; using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Helpers; namespace Ray.EssayNotes.AutoFac.CoreApi { public class Startup { public IConfiguration Configuration { get; } public Startup(IConfiguration configuration) { Configuration = configuration; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); //註冊 //自定義註冊 //註冊倉儲 services.AddScoped<ITeacherRepository, TeacherRepository>(); services.AddScoped<IStudentRepository, StudentRepository>(); services.AddScoped<IBaseRepository<StudentEntity>, BaseRepository<StudentEntity>>(); services.AddScoped<IBaseRepository<TeacherEntity>, BaseRepository<TeacherEntity>>(); services.AddScoped<IBaseRepository<BookEntity>, BaseRepository<BookEntity>>(); //註冊Service services.AddScoped<IStudentService, StudentService>(); services.AddScoped<ITeacherService, TeacherService>(); services.AddScoped<IBookService, BookService>(); } // 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 { // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseMvc(); } } }
因此,你們一般都會本身去擴展這些註冊方法,以實現一些和AutoFac同樣的便捷的註冊操做,下面我根據反射寫了一個小擴展,寫的比較簡單潦草,能夠參考下:
擴展代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; // using Microsoft.Extensions.DependencyInjection; // using Ray.EssayNotes.AutoFac.Model; using Ray.EssayNotes.AutoFac.Repository.IRepository; using Ray.EssayNotes.AutoFac.Repository.Repository; using Ray.EssayNotes.AutoFac.Service.IService; using Ray.EssayNotes.AutoFac.Service.Service; namespace Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Extensions { /// <summary> /// asp.net core註冊擴展 /// </summary> public static class RegisterExtension { /// <summary> /// 反射批量註冊 /// </summary> /// <param name="services"></param> /// <param name="assembly"></param> /// <param name="serviceLifetime"></param> /// <returns></returns> public static IServiceCollection AddAssemblyServices(this IServiceCollection services, Assembly assembly, ServiceLifetime serviceLifetime = ServiceLifetime.Scoped) { var typeList = new List<Type>();//全部符合註冊條件的類集合 //篩選當前程序集下符合條件的類 List<Type> types = assembly.GetTypes(). Where(t => t.IsClass && !t.IsGenericType)//排除了泛型類 .ToList(); typeList.AddRange(types); if (!typeList.Any()) return services; var typeDic = new Dictionary<Type, Type[]>(); //待註冊集合<class,interface> foreach (var type in typeList) { var interfaces = type.GetInterfaces(); //獲取接口 typeDic.Add(type, interfaces); } //循環實現類 foreach (var instanceType in typeDic.Keys) { Type[] interfaceTypeList = typeDic[instanceType]; if (interfaceTypeList == null)//若是該類沒有實現接口,則以其自己類型註冊 { services.AddServiceWithLifeScoped(null, instanceType, serviceLifetime); } else//若是該類有實現接口,則循環其實現的接口,一一配對註冊 { foreach (var interfaceType in interfaceTypeList) { services.AddServiceWithLifeScoped(interfaceType, instanceType, serviceLifetime); } } } return services; } /// <summary> /// 暴露類型可空註冊 /// (若是暴露類型爲null,則自動以其自己類型註冊) /// </summary> /// <param name="services"></param> /// <param name="interfaceType"></param> /// <param name="instanceType"></param> /// <param name="serviceLifetime"></param> private static void AddServiceWithLifeScoped(this IServiceCollection services, Type interfaceType, Type instanceType, ServiceLifetime serviceLifetime) { switch (serviceLifetime) { case ServiceLifetime.Scoped: if (interfaceType == null) services.AddScoped(instanceType); else services.AddScoped(interfaceType, instanceType); break; case ServiceLifetime.Singleton: if (interfaceType == null) services.AddSingleton(instanceType); else services.AddSingleton(interfaceType, instanceType); break; case ServiceLifetime.Transient: if (interfaceType == null) services.AddTransient(instanceType); else services.AddTransient(interfaceType, instanceType); break; default: throw new ArgumentOutOfRangeException(nameof(serviceLifetime), serviceLifetime, null); } } } }
利用這個擴展,咱們在startup裏就能夠用相似AutoFac的語法來註冊了。
註冊代碼:
using System.Linq; using System.Reflection; // using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; // using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Extensions; using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc.Helpers; namespace Ray.EssayNotes.AutoFac.CoreApi { public class Startup { public IConfiguration Configuration { get; } public Startup(IConfiguration configuration) { Configuration = configuration; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); //註冊 //自定義批量註冊 Assembly[] assemblies = ReflectionHelper.GetAllAssembliesCoreWeb(); //註冊repository Assembly repositoryAssemblies = assemblies.FirstOrDefault(x => x.FullName.Contains(".Repository")); services.AddAssemblyServices(repositoryAssemblies); //註冊service Assembly serviceAssemblies = assemblies.FirstOrDefault(x => x.FullName.Contains(".Service")); services.AddAssemblyServices(serviceAssemblies); } // 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 { // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseMvc(); } } }
其實AutoFac針對.NET Core已經幫咱們集成了一套註冊的擴展,咱們能夠經過兩種方式把AutoFac引入.NET Core:一種是將AutoFac容器做爲輔助容器,與.NET Core的DI共存,咱們能夠同時向兩個容器裏註冊組件;一種是讓AutoFac容器接管.NET Core的DI,註冊時只選擇往Autofac容器中註冊。
下面就分別實現下這兩種引入AutoFac的方式。
先按照以前寫AutoFac容器的方法,新建一個針對Core的AutoFac容器:
using System; // using Microsoft.Extensions.DependencyInjection; // using Autofac; using Autofac.Extensions.DependencyInjection; // using Ray.EssayNotes.AutoFac.Repository.IRepository; using Ray.EssayNotes.AutoFac.Repository.Repository; namespace Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc { /// <summary> /// Core的AutoFac容器 /// </summary> public static class CoreContainer { /// <summary> /// 容器實例 /// </summary> public static IContainer Instance; /// <summary> /// 初始化容器 /// </summary> /// <param name="services"></param> /// <param name="func"></param> /// <returns></returns> public static IServiceProvider Init(IServiceCollection services, Func<ContainerBuilder, ContainerBuilder> func = null) { //新建容器構建器,用於註冊組件和服務 var builder = new ContainerBuilder(); //將Core自帶DI容器內的服務遷移到AutoFac容器 builder.Populate(services); //自定義註冊組件 MyBuild(builder); func?.Invoke(builder); //利用構建器建立容器 Instance = builder.Build(); return new AutofacServiceProvider(Instance); } /// <summary> /// 自定義註冊 /// </summary> /// <param name="builder"></param> public static void MyBuild(this ContainerBuilder builder) { var assemblies = Helpers.ReflectionHelper.GetAllAssembliesCoreWeb(); //註冊倉儲 && Service builder.RegisterAssemblyTypes(assemblies) .Where(cc => cc.Name.EndsWith("Repository") |//篩選 cc.Name.EndsWith("Service")) .PublicOnly()//只要public訪問權限的 .Where(cc => cc.IsClass)//只要class型(主要爲了排除值和interface類型) .AsImplementedInterfaces();//自動以其實現的全部接口類型暴露(包括IDisposable接口) //註冊泛型倉儲 builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)); /* //註冊Controller Assembly[] controllerAssemblies = assemblies.Where(x => x.FullName.Contains(".CoreApi")).ToArray(); builder.RegisterAssemblyTypes(controllerAssemblies) .Where(cc => cc.Name.EndsWith("Controller")) .AsSelf(); */ } } }
在主程序中新建一個StartupWithAutoFac類,用於註冊。
StartupWithAutoFac代碼:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; // using Autofac; // using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc; namespace Ray.EssayNotes.AutoFac.CoreApi { public class StartupWithAutoFac { public IConfiguration Configuration { get; } public StartupWithAutoFac(IConfiguration configuration) { Configuration = configuration; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } /// <summary> /// 利用該方法可使用AutoFac輔助註冊,該方法在ConfigureServices()以後執行,因此當發生覆蓋註冊時,之後者爲準。 /// 不要再利用構建器去建立AutoFac容器了,系統已經接管了。 /// </summary> /// <param name="builder"></param> public void ConfigureContainer(ContainerBuilder builder) { builder.MyBuild(); } // 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 { // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseMvc(); } } }
這裏其餘地方與原startup都相同,只是多了一個ConfigureContainer()方法,在該方法內能夠按照AutoFac的語法進行自由註冊。
而後修改program類,將AutoFac hook進管道,並將StartupWithAutoFac類指定爲註冊入口:
using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; namespace Ray.EssayNotes.AutoFac.CoreApi { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) //第一種:使用自帶DI //.UseStartup<Startup>(); //第二種:添加AutoFac做爲輔助容器 .HookAutoFacIntoPipeline() .UseStartup<StartupWithAutoFac>(); //第三種:添加AutoFac接管依賴注入 //.UseStartup<StartupOnlyAutoFac>(); } }
仍是上面的CoreContainer容器。
主程序新建一個StartupOnlyAutoFac類,
代碼以下:
using System; // using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; // using Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc; namespace Ray.EssayNotes.AutoFac.CoreApi { public class StartupOnlyAutoFac { public IConfiguration Configuration { get; } public StartupOnlyAutoFac(IConfiguration configuration) { Configuration = configuration; } // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); return CoreContainer.Init(services); } // 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 { // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseMvc(); } } }
這裏直接改了ConfigureServices()
方法的返回類型,而後在該方法內直接利用AutoFac註冊。
最後固然也要更改下program類,指定StartupOnlyAutoFac類爲註冊入口。
代碼:
using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; namespace Ray.EssayNotes.AutoFac.CoreApi { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) //第一種:使用自帶DI //.UseStartup<Startup>(); //第二種:添加AutoFac做爲輔助容器 //.HookAutoFacIntoPipeline() //.UseStartup<StartupWithAutoFac>(); //第三種:添加AutoFac接管依賴注入 .UseStartup<StartupOnlyAutoFac>(); } }
using Microsoft.AspNetCore.Mvc; // using Ray.EssayNotes.AutoFac.Service.IService; namespace Ray.EssayNotes.AutoFac.CoreApi.Controllers { [ApiController] public class StudentController : ControllerBase { private readonly IStudentService _studentService; public StudentController(IStudentService studentService) { _studentService = studentService; } [Route("Student/GetStuNameById")] public string GetStuNameById(long id) { return _studentService.GetStuName(id); } } }