.Net Core實現模塊化批量注入
我將新建一個項目從頭開始項目名稱Sukt.Core.
該項目分層如下:
-
Sukt.Core.API
為前端提供APi接口(里面盡量不存在業務邏輯,僅能引用應用層,不可跨層引用)
-
Sukt.Core.Application 應用層實現(主要存放業務邏輯,以及數據持久層調用)
-
Sukt.Core.Application.Contracts 應用契約層(存放應用層的接口定義)
-
Sukt.Core.Test 單元測試層
-
Sukt.Core.Dtos MVVM層,(用於存放與前端交互模型,盡量避免前端直接調用實體模型)
-
Sukt.Core.EntityFrameworkCore 數據持久化層,用來存放調用數據庫實現
-
Sukt.Core.Shared (本層最大,本層可以被任意層引用,本層包括了一些自定義擴展)[基礎模塊注冊放到本層/需要自定義擴展模塊需要引用本層的SuktAppModuleBase基礎類 ]
-
基於官方DI實現模塊化批量自動注入
階段一
我們都知道官方的DI沒有批量注入;那么我們第一階段的目標就是來實現這個功能;在實現這個功能前;我們先來講一下模塊化;我們的標題就是實現模塊化注冊;
我們都知道abp框架他的框架在Startup中有很少的代碼;可以說幾乎沒有;那么他是怎么做到的呢?
他是將需要在Startup中需要都封裝成了一個個模塊;需要哪些直接去引用這些模塊就行了;簡化了Startup中的代碼;如果添加了某個模塊代碼報錯了只需要刪除這個模塊來確定錯誤原因,這樣很快就能找到問題所在;不需要像之前一樣一個個去注釋這些服務的注冊;來確定問題原因;這時候有人說了;我直接寫靜態擴展不就好了嘛,此處不抬杠,每個人有個人的編碼愛好;本項目只講知識點。
第一步我們要了解如何實現批量注入的原理;這個原理就是利用反射原理;有人說反射性能慢,但是反射能做的東西有很多;例如反射獲取這個類的特性、父類、接口等等;
我們都知道依賴注入有三種生命周期分別是:Singleton(單例)、Scoped(作用域)和Transient(瞬時)
本文我們只講解Sukt.Core.Shared層的實現功能以及簡單原理;(講真我理論知識也不好半吊子,以后大家多多帶帶我;開個玩笑);到此結束;
-
進入正文
我們都知道批量注入實現的原理其實就是獲取程序集然后通過反射來進行注入;那么我們的程序在進行開發的時候需要引用一些第三方的程序集這時候我們需要把這些程序集過濾掉我們來看代碼(一下代碼就是獲取程序集的)
public static class AssemblyHelper { /// <summary> /// 獲取項目程序集,排除所有的系統程序集(Microsoft.***、System.***等)、Nuget下載包 /// </summary> /// <returns></returns> private static IList<Assembly> GetAllAssemblies() { string[] filters = { "mscorlib", "netstandard", "dotnet", "api-ms-win-core", "runtime.", "System", "Microsoft", "Window", }; List<Assembly> list = new List<Assembly>(); var deps = DependencyContext.Default; //排除所有的系統程序集、Nuget下載包 var libs = deps.CompileLibraries.Where(lib => !lib.Serviceable && lib.Type != "package" && !filters.Any(lib.Name.StartsWith)); try { foreach (var lib in libs) { var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name)); list.Add(assembly); } } catch (Exception ex) { throw ex; } return list; } public static Assembly[] FindAllItems() { return GetAllAssemblies().ToArray(); } }
階段二 創建基礎模塊
我們的基礎模塊有三部分組成
接口:ISuktAppModuleManager、實現:SuktAppModuleManager、服務基類:SuktAppModuleBase
SuktAppModuleBase我們定義成為抽象類里面包含兩個方法分別是之前在Startup中看到的ConfigureServices和Configure;其中Configure方法傳遞的參數和Startup略有不同我們看代碼
/// <summary> /// start服務模塊基類 /// </summary> public abstract class SuktAppModuleBase { /// <summary> /// 將模塊服務添加到依賴注入服務容器中 /// </summary> /// <param name="service">依賴注入服務容器</param> /// <returns></returns> public virtual IServiceCollection ConfigureServices(IServiceCollection service) { return service; } /// <summary> /// Http管道方法由運行時調用;使用此方法配置HTTP請求管道。 /// </summary> public virtual void Configure(IApplicationBuilder applicationBuilder) { } }
ISuktAppModuleManager和SuktAppModuleManager是模塊管理中心接口包含兩個方法一個屬性分別是:SuktSourceModules屬性;LoadModules和Configure方法:以下是兩個方法的代碼:
/// <summary> /// SuktAppModule管理實現 /// </summary> public class SuktAppModuleManager : ISuktAppModuleManager { public List<SuktAppModuleBase> SuktSourceModules { get; private set; } public SuktAppModuleManager() { SuktSourceModules = new List<SuktAppModuleBase>(); } public IServiceCollection LoadModules(IServiceCollection services) { var typeFinder = services.GetOrAddSingletonService<ITypeFinder, TypeFinder>(); var baseType = typeof(SuktAppModuleBase);//反射基類模塊 var moduleTypes = typeFinder.Find(x => x.IsSubclassOf(baseType) && !x.IsAbstract).Distinct().ToArray();///拿到SuktAppModuleBase的所有派生類並且不是抽象類 if(moduleTypes?.Count()<=0) { throw new SuktAppException("需要加載的模塊未找到!!!"); } SuktSourceModules.Clear(); var moduleBases = moduleTypes.Select(m => (SuktAppModuleBase)Activator.CreateInstance(m)); SuktSourceModules.AddRange(moduleBases); List<SuktAppModuleBase> modules = SuktSourceModules.ToList(); foreach (var module in modules) { services = module.ConfigureServices(services); } return services; } public void Configure(IApplicationBuilder applicationBuilder) { foreach (var module in SuktSourceModules) { module.Configure(applicationBuilder); } } }
這些都是前期准備工作,下面我們進入正文,其中有些方法或者特性是我自己擴展的就不一一類據了,畢竟代碼太多了如果有需要敬請去Github下載源代碼;
階段三 模塊化批量自動注入
我們先創建一個DependencyAppModule類,繼承與SuktAppModuleBase基類;
前面我們講了我們有一個SuktAppModuleBase的基類;那么我們在擴展自動注入的時候就要用到這個基類里面的方法了,如果我們不繼承這個基類也可以實現但是那種就不是我們想要的模塊化注冊了,所以我們需要繼承這個基類,首先我們都知道基類里面其實包括之前在Startup看到的兩個方法那么我們的DependencyAppModule類需要重寫里面的ConfigureServices方法;下面是代碼實現
/// <summary> /// 自動注入模塊,繼承與SuktAppModuleBase類進行實現 /// </summary> public class DependencyAppModule:SuktAppModuleBase { public override IServiceCollection ConfigureServices(IServiceCollection service) { SuktIocManage.Instance.SetServiceCollection(service);//寫入服務集合 this.BulkIntoServices(service); return service; } /// <summary> /// 批量注入服務 /// </summary> /// <param name="services"></param> private void BulkIntoServices(IServiceCollection services) { var typeFinder = services.GetOrAddSingletonService<ITypeFinder, TypeFinder>(); typeFinder.NotNull(nameof(typeFinder)); Type[] dependencyTypes = typeFinder.Find(type => type.IsClass && !type.IsAbstract && !type.IsInterface && type.HasAttribute<DependencyAttribute>()); foreach (var dependencyType in dependencyTypes) { AddToServices(services, dependencyType); } } /// <summary> /// 將服務實現類型注冊到服務集合中 /// </summary> /// <param name="services">服務集合</param> /// <param name="implementationType">要注冊的服務實例類型</param> protected virtual void AddToServices(IServiceCollection services, Type implementationType) { var atrr = implementationType.GetAttribute<DependencyAttribute>(); Type[] serviceTypes = implementationType.GetImplementedInterfaces().Where(o => !o.HasAttribute<IgnoreDependencyAttribute>()).ToArray(); if (serviceTypes.Length == 0) { services.TryAdd(new ServiceDescriptor(implementationType, implementationType, atrr.Lifetime)); return; } if (atrr?.AddSelf == true) { services.TryAdd(new ServiceDescriptor(implementationType, implementationType, atrr.Lifetime)); } foreach (var interfaceType in serviceTypes) { services.Add(new ServiceDescriptor(interfaceType, implementationType, atrr.Lifetime)); } } }
在這里我們看到了另一個類SuktIocManage那么我們這個類是干嘛的呢?其實這個很簡單我們只需要看一下里面的代碼就好:看到類的名稱我們就能想象到了;IocManage 就是容器管理嘛,我們在這里其實是為了獲取服務;有些地方用不了注入就需要用到這個服務了。
/// <summary> /// IOC管理 /// </summary> public class SuktIocManage { /// <summary> /// 服務提供者 /// </summary> private IServiceProvider _provider; /// <summary> /// 服務集合 /// </summary> private IServiceCollection _services; /// <summary> /// 創建懶加載Ioc管理實例 /// </summary> private static readonly Lazy<SuktIocManage> SuktInstanceLazy = new Lazy<SuktIocManage>(() => new SuktIocManage()); /// <summary> /// 構造方法 /// </summary> private SuktIocManage() { } public static SuktIocManage Instance => SuktInstanceLazy.Value; /// <summary> /// 設置應用程序服務提供者 /// </summary> internal void SetApplicationServiceProvider(IServiceProvider provider) { _provider.NotNull(nameof(provider)); _provider = provider; } /// <summary> /// 設置應用程序服務集合 /// </summary> internal void SetServiceCollection(IServiceCollection services) { services.NotNull(nameof(services)); _services = services; } }
到這里我們都看到了我再反射或程序集的時候用到了兩個特性,分別是DependencyAttribute和IgnoreDependencyAttribute
IgnoreDependencyAttribute特性是不需要注冊的服務;
DependencyAttribute是需要注冊的服務;
我們可以看下這兩個特性的代碼:
/// <summary> /// 配置此特性將自動進行注入 /// </summary> [AttributeUsage(AttributeTargets.Class)] public class DependencyAttribute:Attribute { /// <summary> /// 構造函數 /// </summary> /// <param name="lifetime">注入類型(Scoped\Singleton\Transient)</param> public DependencyAttribute(ServiceLifetime lifetime) { Lifetime = lifetime; } /// <summary> /// 獲取 生命周期類型,代替 /// <see cref="ISingletonDependency"/>,<see cref="IScopeDependency"/>,<see cref="ITransientDependency"/>三個接口的作用 /// </summary> public ServiceLifetime Lifetime { get; } /// <summary> /// 獲取或設置 是否注冊自身類型,默認沒有接口的類型會注冊自身,當此屬性值為true時,也會注冊自身 /// </summary> public bool AddSelf { get; set; } } /// <summary> /// 配置此特性將忽略依賴注入自動映射 /// </summary> [AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)] public class IgnoreDependencyAttribute:Attribute { }
到這里我們的模塊化批量自動注入基本上完結了以上是部分代碼;整體代碼請移步至
[ Github] https://github.com/GeorGeWzw/Sukt.Core Sukt.Core源碼那么我們怎么確定我們的服務是否可行呢?這時候我們需要用到單元測試了;
我們來創建一個單元測試項目Sukt.Core.Test 這個單元測試是Xunit的切忌單元測試需要和sln文件同級,不然會報錯;單元測試包括以下幾個文件SuktTestServerFixtureBase、SuktWebApplicationFactory和SuktTestStartup
public class SuktDependencyModuleTest: IClassFixture<SuktWebApplicationFactory<SuktTestServerFixtureBase>> { private readonly SuktWebApplicationFactory<SuktTestServerFixtureBase> _factory = null; public SuktDependencyModuleTest(SuktWebApplicationFactory<SuktTestServerFixtureBase> factory) { _factory = factory; } [Fact] public void Test_BulkInjection() { var provider = _factory.Server.Services; var test = provider.GetService<ITestScopedService>(); Assert.NotNull(test); var getTest = test.GetTest(); Assert.Equal("Test", getTest); var testTransientService = provider.GetService<ITestTransientService>(); Assert.NotNull(testTransientService); var transient = testTransientService.GetTransientService(); Assert.NotNull(transient); var testSingleton = provider.GetService<TestSingleton>(); Assert.NotNull(testSingleton); var testService = provider.GetService<ITestService<User>>(); Assert.NotNull(testService); } } public interface ITestScopedService { string GetTest(); } [Dependency(ServiceLifetime.Scoped)] public class TestScopedService : ITestScopedService { public string GetTest(){ return "Test";} } public interface ITestTransientService{string GetTransientService();} [Dependency(ServiceLifetime.Transient)] public class TestTransientService : ITestTransientService { public string GetTransientService(){return "測試瞬時注入成功"} } [Dependency(ServiceLifetime.Singleton)] public class TestSingleton { } public interface ITestService<User> { } [Dependency(ServiceLifetime.Scoped)] public class TestService : ITestService<User> { } public class User { }
-
- 思路來源:感謝大黃瓜;感謝老張的博客指導我入門
