在咱們編寫ASP.NET Core代碼的時候,老是離不開依賴注入這東西。並且對於這一塊,咱們有很是多的選擇,好比:M$ 的DI,Autofac,Ninject,Windsor 等。github
因爲M$自帶了一個DI框架,因此通常狀況下都會優先使用。雖然說功能不是特別全,但也基本知足使用了。框架
正常狀況下(包括好多示例代碼),在要註冊的服務數量比較少時,咱們會選擇一個一個的去註冊。ide
比如下面的示例:post
services.AddTransient<IUserRepository, UserRepository>(); services.AddTransient<IUserService, UserService>();
在數量小於5個的時候,這樣的作法還能夠接受,可是,數量一多,還這樣子秀操做,可就有點接受不了了。ui
可能會常常出現這樣的問題,新加了一個東西,忘記在Startup
上面註冊,下一秒獲得的就是相似下面的錯誤:spa
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)
這樣一來一回,其實也是挺浪費時間的。.net
爲了不這種狀況,咱們每每會根據規律在註冊的時候,用反射進行批量註冊,後面按照對應的規律去寫業務代碼,就能夠避免上面這種問題了。code
對於這個問題,本文將介紹一個擴展庫,來幫咱們簡化這些操做。接口
Scrutor是 Kristian Hellang 大神寫的一個基於Microsoft.Extensions.DependencyInjection的一個擴展庫,主要是爲了簡化咱們對DI的操做。
Scrutor主要提供了兩個擴展方法給咱們使用,一個是Scan
,一個是Decorate
。
本文主要講的是Scan
這個方法。
這種情形應該是咱們用的最多的一種,因此優先來講這種狀況。
假設咱們有下面幾個接口和實現類,
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() );
簡單解釋一下,上面的代碼作了什麼事:
FromAssemblyOf<Startup>
表示加載Startup
這個類所在的程序集AddClasses
表示要註冊那些類,上面的代碼還作了過濾,只留下了以 repository 結尾的類AsImplementedInterfaces
表示將類型註冊爲提供其全部公共接口做爲服務WithTransientLifetime
表示註冊的生命週期爲 Transient若是瞭解過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