用Scrutor来简化ASP.NET Core的DI注册
目錄
- 背景
- Scrutor簡(jiǎn)介
- Scrutor的簡(jiǎn)單使用
- 注冊(cè)接口的實(shí)現(xiàn)類(lèi)
- 注冊(cè)類(lèi)自身
- 重復(fù)注冊(cè)處理策略
- 總結(jié)
- 相關(guān)文章
背景
在我們編寫(xiě)ASP.NET Core代碼的時(shí)候,總是離不開(kāi)依賴注入這東西。而且對(duì)于這一塊,我們有非常多的選擇,比如:M$ 的DI,Autofac,Ninject,Windsor 等。
由于M$自帶了一個(gè)DI框架,所以一般情況下都會(huì)優(yōu)先使用。雖說(shuō)功能不是特別全,但也基本滿足使用了。
正常情況下(包括好多示例代碼),在要注冊(cè)的服務(wù)數(shù)量比較少時(shí),我們會(huì)選擇一個(gè)一個(gè)的去注冊(cè)。
好比下面的示例:
services.AddTransient<IUserRepository, UserRepository>(); services.AddTransient<IUserService, UserService>();在數(shù)量小于5個(gè)的時(shí)候,這樣的做法還可以接受,但是,數(shù)量一多,還這樣子秀操作,可就有點(diǎn)接受不了了。
可能會(huì)經(jīng)常出現(xiàn)這樣的問(wèn)題,新加了一個(gè)東西,忘記在Startup上面注冊(cè),下一秒得到的就是類(lèi)似下面的錯(cuò)誤:
System.InvalidOperationException: Unable to resolve service for type 'ScrutorTest.IProductRepository' while attempting to activate 'ScrutorTest.Controllers.ValuesController'.at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)at lambda_method(Closure , IServiceProvider , Object[] )at Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider.<>c__DisplayClass4_0.<CreateActivator>b__0(ControllerContext controllerContext)at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass5_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)這樣一來(lái)一回,其實(shí)也是挺浪費(fèi)時(shí)間的。
為了避免這種情況,我們往往會(huì)根據(jù)規(guī)律在注冊(cè)的時(shí)候,用反射進(jìn)行批量注冊(cè),后面按照對(duì)應(yīng)的規(guī)律去寫(xiě)業(yè)務(wù)代碼,就可以避免上面這種問(wèn)題了。
對(duì)于這個(gè)問(wèn)題,本文將介紹一個(gè)擴(kuò)展庫(kù),來(lái)幫我們簡(jiǎn)化這些操作。
Scrutor簡(jiǎn)介
Scrutor是 Kristian Hellang 大神寫(xiě)的一個(gè)基于Microsoft.Extensions.DependencyInjection的一個(gè)擴(kuò)展庫(kù),主要是為了簡(jiǎn)化我們對(duì)DI的操作。
Scrutor主要提供了兩個(gè)擴(kuò)展方法給我們使用,一個(gè)是Scan,一個(gè)是Decorate。
本文主要講的是Scan這個(gè)方法。
Scrutor的簡(jiǎn)單使用
注冊(cè)接口的實(shí)現(xiàn)類(lèi)
這種情形應(yīng)該是我們用的最多的一種,所以優(yōu)先來(lái)說(shuō)這種情況。
假設(shè)我們有下面幾個(gè)接口和實(shí)現(xiàn)類(lèi),
public interface IUserService { } public class UserService : IUserService { }public interface IUserRepository { } public class UserRepository : IUserRepository { }public interface IProductRepository { } public class ProductRepository : IProductRepository { }現(xiàn)在我們只需要注冊(cè)UserRepository和ProductRepository,
services.Scan(scan => scan.FromAssemblyOf<Startup>().AddClasses(classes => classes.Where(t=>t.Name.EndsWith("repository",StringComparison.OrdinalIgnoreCase))).AsImplementedInterfaces().WithTransientLifetime());簡(jiǎn)單解釋一下,上面的代碼做了什么事:
如果了解過(guò)Autofac的朋友,看到這樣的寫(xiě)法應(yīng)該很熟悉。
對(duì)于上面的例子,它等價(jià)于下面的代碼
services.AddTransient<IUserRepository, UserRepository>(); services.AddTransient<IProductRepository, ProductRepository>();如果我們?cè)谧?cè)完成后,想看一下我們自己注冊(cè)的信息,可以加上下面的代碼:
var list = services.Where(x => x.ServiceType.Namespace.Equals("ScrutorTest", StringComparison.OrdinalIgnoreCase)).ToList();foreach (var item in list) {Console.WriteLine($"{item.Lifetime},{item.ImplementationType},{item.ServiceType}"); }運(yùn)行dotnet run之后,可以看到下面的輸出
Singleton,ScrutorTest.UserRepository,ScrutorTest.IUserRepository Singleton,ScrutorTest.ProductRepository,ScrutorTest.IProductRepository這個(gè)時(shí)候,如果我們加了一個(gè) IOrderRepository 和 OrderRepostity , 就不需要在Startup上面多寫(xiě)一行注冊(cè)代碼了,Scrutor已經(jīng)幫我們自動(dòng)處理了。
接下來(lái),我們需要把UserService也注冊(cè)進(jìn)去,我們完全可以照葫蘆畫(huà)瓢了。
services.Scan(scan => scan.FromAssemblyOf<Startup>().AddClasses(classes => classes.Where(t=>t.Name.EndsWith("repository",StringComparison.OrdinalIgnoreCase))).AsImplementedInterfaces().WithTransientLifetime());services.Scan(scan => scan.FromAssemblyOf<Startup>().AddClasses(classes => classes.Where(t => t.Name.EndsWith("service", StringComparison.OrdinalIgnoreCase))).AsImplementedInterfaces().WithTransientLifetime());也可以略微簡(jiǎn)單一點(diǎn)點(diǎn),一個(gè)scan里面搞定所有
services.Scan(scan => scan.FromAssemblyOf<Startup>().AddClasses(classes => classes.Where(t=>t.Name.EndsWith("repository",StringComparison.OrdinalIgnoreCase))).AsImplementedInterfaces().WithTransientLifetime().AddClasses(classes => classes.Where(t => t.Name.EndsWith("service", StringComparison.OrdinalIgnoreCase))).AsImplementedInterfaces().WithScopedLifetime()//換一下生命周期);這個(gè)時(shí)候結(jié)果如下:
Transient,ScrutorTest.UserRepository,ScrutorTest.IUserRepository Transient,ScrutorTest.ProductRepository,ScrutorTest.IProductRepository Scoped,ScrutorTest.UserService,ScrutorTest.IUserService雖然效果一樣,但是總想著有沒(méi)有一些更簡(jiǎn)單的方法。
很多時(shí)候,我們寫(xiě)一些接口和實(shí)現(xiàn)類(lèi)的時(shí)候,都會(huì)根據(jù)這樣的習(xí)慣來(lái)命名,定義一個(gè)接口IClass,它的實(shí)現(xiàn)類(lèi)就是Class。
針對(duì)這種情形,Scrutor提供了一個(gè)簡(jiǎn)便的方法來(lái)幫助我們處理。
使用 AsMatchingInterface 方法就可以很輕松的幫我們處理注冊(cè)好對(duì)應(yīng)的信息。
services.Scan(scan => scan.FromAssemblyOf<Startup>().AddClasses().AsMatchingInterface().WithTransientLifetime());這個(gè)時(shí)候會(huì)輸出下面的結(jié)果:
Transient,ScrutorTest.UserService,ScrutorTest.IUserService Transient,ScrutorTest.UserRepository,ScrutorTest.IUserRepository Transient,ScrutorTest.ProductRepository,ScrutorTest.IProductRepository當(dāng)然這種方法也有對(duì)應(yīng)的缺點(diǎn),那就是對(duì)生命周期的控制。舉個(gè)例子,有兩大類(lèi),一大類(lèi)要Transient,一大類(lèi)要Scoped,這個(gè)時(shí)候,我們也只能過(guò)濾掉部分內(nèi)容才能注冊(cè) 。
需要根據(jù)自身的情況來(lái)選擇是否要使用這個(gè)方法,或者什么時(shí)候使用這個(gè)方法。
注冊(cè)類(lèi)自身
有時(shí)候,我們建的一些類(lèi)是沒(méi)有實(shí)現(xiàn)接口的,就純粹是在“裸奔”的那種,然后直接用單例的方式來(lái)調(diào)用。
Scrutor也提供了方法AsSelf來(lái)處理這種情形。
來(lái)看下面這段代碼。
services.Scan(scan => scan.AddTypes(typeof(MyClass)).AsSelf().WithSingletonLifetime());這里和前面的注冊(cè)代碼有一點(diǎn)點(diǎn)差異。
AddTypes是直接加載具體的某個(gè)類(lèi)或一批類(lèi),這個(gè)的作用可以認(rèn)為和FromXxx是一樣的。
它等價(jià)于下面的代碼
services.AddSingleton<MyClass>();相對(duì)來(lái)說(shuō)批量操作的時(shí)候還是有點(diǎn)繁鎖,因?yàn)樾枰衙總€(gè)類(lèi)型都扔進(jìn)去,我們不可能事先知道所有的類(lèi)。
下面的方法可以把MyClass所在的程序集的類(lèi)都注冊(cè)了。
services.Scan(scan => scan.FromAssemblyOf<MyClass>().AddClasses().AsSelf().WithSingletonLifetime());這樣的做法也有一個(gè)缺點(diǎn),會(huì)造成部分我們不想讓他注冊(cè)的,也注冊(cè)進(jìn)去了。
過(guò)濾一下或者規(guī)范一下自己的結(jié)構(gòu),就可以處理這個(gè)問(wèn)題了。
重復(fù)注冊(cè)處理策略
還有一個(gè)比較常見(jiàn)的情形是,重復(fù)注冊(cè),即同一個(gè)接口,有多個(gè)不同的實(shí)現(xiàn)。
Scrutor提供了三大策略,Append、Skip和Replace。 Append是默認(rèn)行為,就是疊加。
下面來(lái)看這個(gè)例子
public interface IDuplicate { } public class FirstDuplicate : IDuplicate { } public class SecondDuplicate : IDuplicate { } services.Scan(scan => scan.FromAssemblyOf<Startup>() .AddClasses(classes=>classes.AssignableTo<IDuplicate>()).AsImplementedInterfaces().WithTransientLifetime() );這個(gè)時(shí)候的輸出如下
Transient,ScrutorTest.FirstDuplicate,ScrutorTest.IDuplicate Transient,ScrutorTest.SecondDuplicate,ScrutorTest.IDuplicate下面我們用Skip策略來(lái)替換默認(rèn)的策略
services.Scan(scan => scan.FromAssemblyOf<Startup>() .AddClasses(classes=>classes.AssignableTo<IDuplicate>())//手動(dòng)高亮.UsingRegistrationStrategy(RegistrationStrategy.Skip).AsImplementedInterfaces().WithTransientLifetime() );這個(gè)時(shí)候的輸出如下
Transient,ScrutorTest.FirstDuplicate,ScrutorTest.IDuplicate可見(jiàn)得到的結(jié)果確實(shí)沒(méi)有了第二個(gè)注冊(cè)。
總結(jié)
Scrutor的Scan方法確實(shí)很方便,可以讓我們很容易的擴(kuò)展M$ 的DI。
當(dāng)然Scrutor還有其他的用法,詳細(xì)的可以參考它的Github頁(yè)面。
相關(guān)文章
Introducing Scrutor - Convention based registration for Microsoft.Extensions.DependencyInjection
Using Scrutor to automatically register your services with the ASP.NET Core DI container
轉(zhuǎn)載于:https://www.cnblogs.com/catcher1994/p/10316928.html
總結(jié)
以上是生活随笔為你收集整理的用Scrutor来简化ASP.NET Core的DI注册的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 蜂鸟智游大数据:“人在囧途”的春运,航空
- 下一篇: 单例模式---设计模式(一)