用Scrutor来简化ASP.NET Core的DI注册
目錄
- 背景
- Scrutor簡介
- Scrutor的簡單使用
- 注冊接口的實現類
- 注冊類自身
- 重復注冊處理策略
- 總結
- 相關文章
背景
在我們編寫ASP.NET Core代碼的時候,總是離不開依賴注入這東西。而且對于這一塊,我們有非常多的選擇,比如:M$ 的DI,Autofac,Ninject,Windsor 等。
由于M$自帶了一個DI框架,所以一般情況下都會優先使用。雖說功能不是特別全,但也基本滿足使用了。
正常情況下(包括好多示例代碼),在要注冊的服務數量比較少時,我們會選擇一個一個的去注冊。
好比下面的示例:
services.AddTransient<IUserRepository, UserRepository>(); services.AddTransient<IUserService, UserService>();在數量小于5個的時候,這樣的做法還可以接受,但是,數量一多,還這樣子秀操作,可就有點接受不了了。
可能會經常出現這樣的問題,新加了一個東西,忘記在Startup上面注冊,下一秒得到的就是類似下面的錯誤:
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)這樣一來一回,其實也是挺浪費時間的。
為了避免這種情況,我們往往會根據規律在注冊的時候,用反射進行批量注冊,后面按照對應的規律去寫業務代碼,就可以避免上面這種問題了。
對于這個問題,本文將介紹一個擴展庫,來幫我們簡化這些操作。
Scrutor簡介
Scrutor是 Kristian Hellang 大神寫的一個基于Microsoft.Extensions.DependencyInjection的一個擴展庫,主要是為了簡化我們對DI的操作。
Scrutor主要提供了兩個擴展方法給我們使用,一個是Scan,一個是Decorate。
本文主要講的是Scan這個方法。
Scrutor的簡單使用
注冊接口的實現類
這種情形應該是我們用的最多的一種,所以優先來說這種情況。
假設我們有下面幾個接口和實現類,
public interface IUserService { } public class UserService : IUserService { }public interface IUserRepository { } public class UserRepository : IUserRepository { }public interface IProductRepository { } public class ProductRepository : IProductRepository { }現在我們只需要注冊UserRepository和ProductRepository,
services.Scan(scan => scan.FromAssemblyOf<Startup>().AddClasses(classes => classes.Where(t=>t.Name.EndsWith("repository",StringComparison.OrdinalIgnoreCase))).AsImplementedInterfaces().WithTransientLifetime());簡單解釋一下,上面的代碼做了什么事:
如果了解過Autofac的朋友,看到這樣的寫法應該很熟悉。
對于上面的例子,它等價于下面的代碼
services.AddTransient<IUserRepository, UserRepository>(); services.AddTransient<IProductRepository, ProductRepository>();如果我們在注冊完成后,想看一下我們自己注冊的信息,可以加上下面的代碼:
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}"); }運行dotnet run之后,可以看到下面的輸出
Singleton,ScrutorTest.UserRepository,ScrutorTest.IUserRepository Singleton,ScrutorTest.ProductRepository,ScrutorTest.IProductRepository這個時候,如果我們加了一個 IOrderRepository 和 OrderRepostity , 就不需要在Startup上面多寫一行注冊代碼了,Scrutor已經幫我們自動處理了。
接下來,我們需要把UserService也注冊進去,我們完全可以照葫蘆畫瓢了。
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());也可以略微簡單一點點,一個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()//換一下生命周期);這個時候結果如下:
Transient,ScrutorTest.UserRepository,ScrutorTest.IUserRepository Transient,ScrutorTest.ProductRepository,ScrutorTest.IProductRepository Scoped,ScrutorTest.UserService,ScrutorTest.IUserService雖然效果一樣,但是總想著有沒有一些更簡單的方法。
很多時候,我們寫一些接口和實現類的時候,都會根據這樣的習慣來命名,定義一個接口IClass,它的實現類就是Class。
針對這種情形,Scrutor提供了一個簡便的方法來幫助我們處理。
使用 AsMatchingInterface 方法就可以很輕松的幫我們處理注冊好對應的信息。
services.Scan(scan => scan.FromAssemblyOf<Startup>().AddClasses().AsMatchingInterface().WithTransientLifetime());這個時候會輸出下面的結果:
Transient,ScrutorTest.UserService,ScrutorTest.IUserService Transient,ScrutorTest.UserRepository,ScrutorTest.IUserRepository Transient,ScrutorTest.ProductRepository,ScrutorTest.IProductRepository當然這種方法也有對應的缺點,那就是對生命周期的控制。舉個例子,有兩大類,一大類要Transient,一大類要Scoped,這個時候,我們也只能過濾掉部分內容才能注冊 。
需要根據自身的情況來選擇是否要使用這個方法,或者什么時候使用這個方法。
注冊類自身
有時候,我們建的一些類是沒有實現接口的,就純粹是在“裸奔”的那種,然后直接用單例的方式來調用。
Scrutor也提供了方法AsSelf來處理這種情形。
來看下面這段代碼。
services.Scan(scan => scan.AddTypes(typeof(MyClass)).AsSelf().WithSingletonLifetime());這里和前面的注冊代碼有一點點差異。
AddTypes是直接加載具體的某個類或一批類,這個的作用可以認為和FromXxx是一樣的。
它等價于下面的代碼
services.AddSingleton<MyClass>();相對來說批量操作的時候還是有點繁鎖,因為需要把每個類型都扔進去,我們不可能事先知道所有的類。
下面的方法可以把MyClass所在的程序集的類都注冊了。
services.Scan(scan => scan.FromAssemblyOf<MyClass>().AddClasses().AsSelf().WithSingletonLifetime());這樣的做法也有一個缺點,會造成部分我們不想讓他注冊的,也注冊進去了。
過濾一下或者規范一下自己的結構,就可以處理這個問題了。
重復注冊處理策略
還有一個比較常見的情形是,重復注冊,即同一個接口,有多個不同的實現。
Scrutor提供了三大策略,Append、Skip和Replace。 Append是默認行為,就是疊加。
下面來看這個例子
public interface IDuplicate { } public class FirstDuplicate : IDuplicate { } public class SecondDuplicate : IDuplicate { } services.Scan(scan => scan.FromAssemblyOf<Startup>() .AddClasses(classes=>classes.AssignableTo<IDuplicate>()).AsImplementedInterfaces().WithTransientLifetime() );這個時候的輸出如下
Transient,ScrutorTest.FirstDuplicate,ScrutorTest.IDuplicate Transient,ScrutorTest.SecondDuplicate,ScrutorTest.IDuplicate下面我們用Skip策略來替換默認的策略
services.Scan(scan => scan.FromAssemblyOf<Startup>() .AddClasses(classes=>classes.AssignableTo<IDuplicate>())//手動高亮.UsingRegistrationStrategy(RegistrationStrategy.Skip).AsImplementedInterfaces().WithTransientLifetime() );這個時候的輸出如下
Transient,ScrutorTest.FirstDuplicate,ScrutorTest.IDuplicate可見得到的結果確實沒有了第二個注冊。
總結
Scrutor的Scan方法確實很方便,可以讓我們很容易的擴展M$ 的DI。
當然Scrutor還有其他的用法,詳細的可以參考它的Github頁面。
相關文章
Introducing Scrutor - Convention based registration for Microsoft.Extensions.DependencyInjection
Using Scrutor to automatically register your services with the ASP.NET Core DI container
轉載于:https://www.cnblogs.com/catcher1994/p/10316928.html
總結
以上是生活随笔為你收集整理的用Scrutor来简化ASP.NET Core的DI注册的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 蜂鸟智游大数据:“人在囧途”的春运,航空
- 下一篇: 机器学习入门-文本数据-使用聚类增加文本