由於咱們的系統每每時面向接口編程的,因此在開發Asp .net core項目的時候,必定會有大量大接口及其對應的實現要在ConfigureService
註冊到ServiceCollection
中,傳統的作法是加了一個服務,咱們就要註冊一次(service.AddService()
),又好比,當一個接口有多個實現,在構造函數中獲取服務也不是很友好,而據我所知, .Net Core目前是沒有什麼自帶的庫或者方法解決這些問題,固然,若是引入第三方容器如AutoFac這些問題時能迎刃而解的,可是如何在不引入第三方容器來解決這個問題呢?
因此我就設計了這樣的一個輕量級工具.html
首先,放上該項目的Github地址(記得Star哦!!)git
https://github.com/liuzhenyulive/CodeDigithub
CodeDi是一個基於 .Net Standard的工具庫,它能幫助咱們自動地在Asp .net core或者 .net core項目中完成服務的註冊.編程
CodeDi 是 Code Dependency Injection的意思,在上次我在看了由依樂祝寫的<.NET Core中的一個接口多種實現的依賴注入與動態選擇看這篇就夠了>後,回想起我以前遇到的那些問題,感受撥雲見日,因此,我就開始着手寫這個工具了.json
CodeDi的Nuget包已經發布到了 nuget.org,您能夠經過如下指令在您的項目中安裝CodeDiapp
PM> Install-Package CodeDi
您能夠在Startup
的ConfigureService
方法中添加AddCodeDi完成對CodeDi的調用.服務的註冊CodeDi會自動爲您完成.ide
public void ConfigureServices(IServiceCollection services) { services.AddCoreDi(); services.AddMvc(); }
您也能夠在AddCodeDi方法中傳入一個Action<CodeDiOptions>
參數,在這個action中,您能夠對CodeDiOptions的屬性進行配置.函數
public void ConfigureServices(IServiceCollection services) { services.AddCoreDi(options => { options.DefaultServiceLifetime = ServiceLifetime.Scoped; }); services.AddMvc(); }
固然您也能夠直接給AddCodeDi()
方法直接傳入一個CodeDiOptions
實例.工具
public void ConfigureServices(IServiceCollection services) { services.AddCoreDi(new CodeDiOptions() { DefaultServiceLifetime = ServiceLifetime.Scoped }); services.AddMvc(); }
你也能夠在appsetting.json
文件中配置CodeDiOptions
的信息,並經過Configuration.Bind("CodeDiOptions", options)
把配置信息綁定到一個CodeDiOptions
實例.ui
appsetting.json file
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "CodeDiOptions": { "DefaultServiceLifetime": 1, "AssemblyNames": [ "*CodeDi" ], "AssemblyPaths": [ "C:\\MyBox\\Github\\CodeDI\\CodeDI\\bin\\Debug\\netstandard2.0" ], "IgnoreAssemblies": [ "*Test" ], "IncludeSystemAssemblies": false, "IgnoreInterface": [ "*Say" ], "InterfaceMappings": { "*Say": "*English" }, "ServiceLifeTimeMappings": { "*Say": 0 } } }
ConfigureService方法
public void ConfigureServices(IServiceCollection services) { var options=new CodeDiOptions(); Configuration.Bind("CodeDiOptions", options); services.AddCoreDi(options); services.AddMvc(); }
屬性名稱 | 屬性描述 | 數據類型 | 默認值 |
---|---|---|---|
AssemblyPaths | 在指定目錄下加載Dll程序集 | string[] | Bin目錄 |
AssemblyNames | 選擇要加載的程序集名稱 (支持通配符) | string[] | * |
IgnoreAssemblies | 忽略的程序集名稱 (支持通配符) | string[] | null |
IncludeSystemAssemblies | 是否包含系統程序集(當爲false時,會忽略含有System,Microsoft,CppCodeProvider,WebMatrix,SMDiagnostics,Newtonsoft關鍵詞和在App_Web,App_global目錄下的程序集) | bool | false |
IgnoreInterface | 忽略的接口 (支持通配符) | string[] | null |
InterfaceMappings | 接口對應的服務 (支持通配符) ,當一個接口有多個實現時,若是不進行配置,則多個實現都會註冊到SerciceCollection中 | Dictionary<string, string> | null |
DefaultServiceLifetime | 默認的服務生命週期 | ServuceLifetime( Singleton,Scoped,Transient) | ServiceLifetime.Scope |
ServiceLifeTimeMappings | 指定某個接口的服務生命週期,不指定爲默認的生命週期 | Dictionary<string, ServiceLifetime> | null |
若是 ISay
接口有SayInChinese
和SayInEnglish
兩個實現,咱們只想把SayInEnglish註冊到ServiceCollection
中
public interface ISay { string Hello(); } public class SayInChinese:ISay { public string Hello() { return "您好"; } } public class SayInEnglish:ISay { public string Hello() { return "Hello"; } }
那麼咱們能夠這樣配置InterfaceMappings
.
options.InterfaceMappings=new Dictionary<string, string>(){{ "ISay", "SayInChinese" } }
也就是{接口名稱
(支持通配符),實現名稱
(支持通配符)}
若是咱們但願ISay接口的服務的生命週期爲Singleton
,咱們能夠這樣配置ServiceLifeTimeMappings
.
options.ServiceLifeTimeMappings = new Dictionary<string, ServiceLifetime>(){{"*Say",ServiceLifetime.Singleton}};
也就是也就是{接口名稱
(支持通配符),Servicelifetime
}
關於ServiceLifetime: https://github.com/aspnet/DependencyInjection/blob/master/src/DI.Abstractions/ServiceLifetime.cs
固然, 您能夠和以前同樣,直接在構造函數中進行依賴的注入,可是當某個接口有多個實現並且都註冊到了ServiceCollection中,獲取就沒有那麼方便了,您能夠用ICodeDiServiceProvider
來幫助您獲取服務實例.
例如,當 ISay
接口有 SayInChinese
和 SayInEnglish
兩個實現, 咱們咱們如何獲取咱們想要的服務實例呢?
public interface ISay { string Hello(); } public class SayInChinese:ISay { public string Hello() { return "您好"; } } public class SayInEnglish:ISay { public string Hello() { return "Hello"; } }
public class HomeController : Controller { private readonly ISay _say; public HomeController(ICodeDiServiceProvider serviceProvider) { _say = serviceProvider.GetService<ISay>("*Chinese"); } public string Index() { return _say.Hello(); } }
ICodeDiServiceProvider.GetService<T>(string name=null)
參數中的Name支持通配符.
既然是一個輕量級工具
,那麼實現起來天然不會太複雜,我來講說比較核心的代碼.
private Dictionary<Type, List<Type>> GetInterfaceMapping(IList<Assembly> assemblies) { var mappings = new Dictionary<Type, List<Type>>(); var allInterfaces = assemblies.SelectMany(u => u.GetTypes()).Where(u => u.IsInterface); foreach (var @interface in allInterfaces) { mappings.Add(@interface, assemblies.SelectMany(a => a.GetTypes(). Where(t => t.GetInterfaces().Contains(@interface) ) ) .ToList()); } return mappings; }
GetInterfaceMapping經過反射機制,首先獲取程序集中的全部接口allInterfaces
,而後遍歷allInterfaces
找到該接口對應的實現,最終,該方法返回接口和實現的匹配關係,爲Dictionary<Type, List
private void AddToService(Dictionary<Type, List<Type>> interfaceMappings) { foreach (var mapping in interfaceMappings) { if (mapping.Key.FullName == null || (_options.IgnoreInterface != null && _options.IgnoreInterface.Any(i => mapping.Key.FullName.Matches(i)))) continue; if (mapping.Key.FullName != null && _options.InterfaceMappings != null && _options.InterfaceMappings.Any(u => mapping.Key.FullName.Matches(u.Key))) { foreach (var item in mapping.Value.Where(value => value.FullName != null). Where(value => value.FullName.Matches(_options.InterfaceMappings.FirstOrDefault(u => mapping.Key.FullName.Matches(u.Key)).Value))) { AddToService(mapping.Key, item); } continue; } foreach (var item in mapping.Value) { AddToService(mapping.Key, item); } } }
該方法要判斷CodeDiOptions中是否忽略了該接口,同時,是否指定實現映射關係.
什麼叫實現映射關係呢?參見InterfaceMappings
若是指定了,那麼就按指定的來實現,若是沒指定,就會把每一個實現都註冊到ServiceCollection中.
private readonly IServiceCollection _service; private readonly CodeDiOptions _options; private readonly ServiceDescriptor[] _addedService; public CodeDiService(IServiceCollection service, CodeDiOptions options) { _service = service ?? throw new ArgumentNullException(nameof(service)); _options = options ?? new CodeDiOptions(); _addedService = new ServiceDescriptor[service.Count]; service.CopyTo(_addedService, 0); //在構造函數中,咱們經過這種方式把Service中已經添加的服務讀取出來 //後面進行服務註冊時,會進行判斷,避免重複添加 } private void AddToService(Type serviceType, Type implementationType) { ServiceLifetime serviceLifetime; try { serviceLifetime = _options.DefaultServiceLifetime; if (_options.ServiceLifeTimeMappings != null && serviceType.FullName != null) { var lifeTimeMapping = _options.ServiceLifeTimeMappings.FirstOrDefault(u => serviceType.FullName.Matches(u.Key)); serviceLifetime = lifeTimeMapping.Key != null ? lifeTimeMapping.Value : _options.DefaultServiceLifetime; } } catch { throw new Exception("Service Life Time Only Can be set in range of 0-2"); } if (_addedService.Where(u => u.ServiceType == serviceType).Any(u => u.ImplementationType == implementationType)) return; _service.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime)); }
AddToService中,要判斷有沒有對接口的生命週期進行配置,參見ServiceLifeTimeMappings,若是沒有配置,就按DefaultServiceLifetime進行配置,DefaultServiceLifetime若是沒有修改的狀況下時ServiceLifetime.Scoped,即每一個Request建立一個實例.
private readonly IServiceProvider _serviceProvider; public CodeDiServiceProvider(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public T GetService<T>(string name) where T : class { return _serviceProvider.GetService<IEnumerable<T>>().FirstOrDefault(u => u.GetType().Name.Matches( name)); }
這CodeDiServiceProvider的實現代碼,這裏參考了依樂祝寫的<.NET Core中的一個接口多種實現的依賴注入與動態選擇看這篇就夠了>給出的一種解決方案,即當某個接口註冊了多個實現,其實能夠經過IEnumerable
只要進行一次簡單的CodeDi配置,之後系統中添加了新的接口以及對應的服務實現後,就不用再去一個個地Add到IServiceCollection中了.
若是有問題,歡迎Issue,歡迎PR.
最後,賞個Star唄! 前往Star