用Scrutor来简化ASP.NET Core的DI注册

用Scrutor来简化ASP.NET Core的DI注册,第1张

概述目录背景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());简单解释一下,上面的代码做了什么事: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.IUserRepositorySingleton,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.IUserRepositoryTransient,ScrutorTest.ProductRepository,ScrutorTest.IProductRepositoryScoped,ScrutorTest.UserService,ScrutorTest.IUserService虽然效果一样,但是总想着有没有一些更简单的方法。很多时候,我们写一些接口和实现类的时候,都会根据这样的习惯来命名,定义一个接口IClass,它的实现类就是Class。针对这种情形,Scrutor提供了一个简便的方法来帮助我们处理。使用 AsM

<p >目录

在我们编写ASP.NET Core代码的时候,总是离不开依赖注入这东西。而且对于这一块,我们有非常多的选择,比如:M$ 的DI,autofac,Ninject,Windsor 等。

由于M$自带了一个DI框架,所以一般情况下都会优先使用。虽说功能不是特别全,但也基本满足使用了。

正常情况下(包括好多示例代码),在要注册的服务数量比较少时,我们会选择一个一个的去注册。

好比下面的示例:

services.AddTransIEnt();services.AddTransIEnt();

在数量小于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.b__0(ControllerContext controllerContext)   at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvIDer.<>c__displayClass5_0.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,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)

这样一来一回,其实也是挺浪费时间的。

为了避免这种情况,我们往往会根据规律在注册的时候,用反射进行批量注册,后面按照对应的规律去写业务代码,就可以避免上面这种问题了。

对于这个问题,本文将介绍一个扩展库,来帮我们简化这些 *** 作。

是 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 { }

现在我们只需要注册UserRepositoryProductRepository

services.Scan(scan => scan    .FromAssemblyOf()        .AddClasses(classes => classes.Where(t=>t.Name.EndsWith("repository",StringComparison.OrdinalIgnoreCase)))        .AsImplementedInterfaces()        .WithTransientLifetime()    );

简单解释一下,上面的代码做了什么事:

FromAssemblyOf 表示加载Startup这个类所在的程序集AddClasses 表示要注册那些类,上面的代码还做了过滤,只留下了以 repository 结尾的类AsImplementedInterfaces 表示将类型注册为提供其所有公共接口作为服务WithTransientLifetime 表示注册的生命周期为 Transient

如果了解过Autofac的朋友,看到这样的写法应该很熟悉。

对于上面的例子,它等价于下面的代码

services.AddTransient();services.AddTransient();

如果我们在注册完成后,想看一下我们自己注册的信息,可以加上下面的代码:

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.IUserRepositorySingleton,ScrutorTest.ProductRepository,ScrutorTest.IProductRepository

这个时候,如果我们加了一个 IOrderRepositoryOrderRepostity , 就不需要在Startup上面多写一行注册代码了,Scrutor已经帮我们自动处理了。

接下来,我们需要把UserService也注册进去,我们完全可以照葫芦画瓢了。

services.Scan(scan => scan    .FromAssemblyOf()        .AddClasses(classes => classes.Where(t=>t.Name.EndsWith("repository",StringComparison.OrdinalIgnoreCase)))        .AsImplementedInterfaces()        .WithTransientLifetime()    );

services.Scan(scan => scan
.FromAssemblyOf()
.AddClasses(classes => classes.Where(t => t.Name.EndsWith("service",StringComparison.OrdinalIgnoreCase)))
.AsImplementedInterfaces()
.WithTransientLifetime()
);

也可以略微简单一点点,一个scan里面搞定所有

services.Scan(scan => scan    .FromAssemblyOf()        .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()//换一下生命周期);</code></pre>

这个时候结果如下:

Transient,ScrutorTest.IUserRepositoryTransient,ScrutorTest.IProductRepositoryScoped,ScrutorTest.UserService,ScrutorTest.IUserService

虽然效果一样,但是总想着有没有一些更简单的方法。

很多时候,我们写一些接口和实现类的时候,都会根据这样的习惯来命名,定义一个接口IClass,它的实现类就是Class

针对这种情形,Scrutor提供了一个简便的方法来帮助我们处理。

使用 AsMatchingInterface 方法就可以很轻松的帮我们处理注册好对应的信息。

services.Scan(scan => scan    .FromAssemblyOf()        .AddClasses()            .AsMatchingInterface()            .WithTransientLifetime()    );

这个时候会输出下面的结果:

Transient,ScrutorTest.IUserServiceTransient,ScrutorTest.IProductRepository

当然这种方法也有对应的缺点,那就是对生命周期的控制。举个例子,有两大类,一大类要Transient,一大类要Scoped,这个时候,我们也只能过滤掉部分内容才能注册 。

需要根据自身的情况来选择是否要使用这个方法,或者什么时候使用这个方法。

有时候,我们建的一些类是没有实现接口的,就纯粹是在“裸奔”的那种,然后直接用单例的方式来调用。

Scrutor也提供了方法AsSelf来处理这种情形。

来看下面这段代码。

services.Scan(scan => scan    .AddTypes(typeof(MyClass))        .AsSelf()        .WithSingletonLifetime()    );

这里和前面的注册代码有一点点差异。

AddTypes是直接加载具体的某个类或一批类,这个的作用可以认为和FromXxx是一样的。

它等价于下面的代码

services.AddSingleton();

相对来说批量 *** 作的时候还是有点繁锁,因为需要把每个类型都扔进去,我们不可能事先知道所有的类。

下面的方法可以把MyClass所在的程序集的类都注册了。

services.Scan(scan => scan    .FromAssemblyOf()        .AddClasses()        .AsSelf()        .WithSingletonLifetime()    );

这样的做法也有一个缺点,会造成部分我们不想让他注册的,也注册进去了。

过滤一下或者规范一下自己的结构,就可以处理这个问题了。

还有一个比较常见的情形是,重复注册,即同一个接口,有多个不同的实现。

Scrutor提供了三大策略,Append、Skip和Replace。 Append是默认行为,就是叠加。

下面来看这个例子

public interface IDuplicate { }public class FirstDuplicate : IDuplicate { }public class SecondDuplicate : IDuplicate { }
services.Scan(scan => scan    .FromAssemblyOf()                            .AddClasses(classes=>classes.AssignableTo())            .AsImplementedInterfaces()            .WithTransientLifetime()                    );

这个时候的输出如下

Transient,ScrutorTest.FirstDuplicate,ScrutorTest.IDuplicateTransient,ScrutorTest.SecondDuplicate,ScrutorTest.IDuplicate

下面我们用Skip策略来替换默认的策略

services.Scan(scan => scan    .FromAssemblyOf()                            .AddClasses(classes=>classes.AssignableTo())            //手动高亮            .UsingRegistrationStrategy(RegistrationStrategy.Skip)            .AsImplementedInterfaces()            .WithTransientLifetime()                    );

这个时候的输出如下

Transient,ScrutorTest.IDuplicate

可见得到的结果确实没有了第二个注册。

Scrutor的Scan方法确实很方便,可以让我们很容易的扩展M$ 的DI。

当然Scrutor还有其他的用法,详细的可以参考它的Github页面。

href="https://andrewlock.net/using-scrutor-to-automatically-register-your-services-with-the-asp-net-core-di-container/">Using scrutor to automatically register your services with the ASP.NET Core DI container

总结

以上是内存溢出为你收集整理的用Scrutor来简化ASP.NET Core的DI注册全部内容,希望文章能够帮你解决用Scrutor来简化ASP.NET Core的DI注册所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/1256053.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-07
下一篇 2022-06-07

发表评论

登录后才能评论

评论列表(0条)

保存