背景

在我们编写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 { }

现在我们只需要注册UserRepositoryProductRepository

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

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

  1. FromAssemblyOf<Startup> 表示加载Startup这个类所在的程序集
  2. AddClasses 表示要注册那些类,上面的代码还做了过滤,只留下了以 repository 结尾的类
  3. AsImplementedInterfaces 表示将类型注册为提供其所有公共接口作为服务
  4. 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

这个时候,如果我们加了一个 IOrderRepositoryOrderRepostity , 就不需要在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提供了三大策略,AppendSkipReplace。 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

用Scrutor来简化ASP.NET Core的DI注册的更多相关文章

  1. Asp.Net Core中DI的知识总结

    在asp.net core中DI的概念是由这几部分组成的: IServiceCollection,保存IServiceDescriptor实例的列表 IServiceProvider,只有一个方法Ge ...

  2. asp.net core的DI框架思考以及服务实例的获取方式总结

    转载请注明出处: https://home.cnblogs.com/u/zhiyong-ITNote/ 整个asp.net core管道从WebHostBuilder到WebHost到后续请求的类中, ...

  3. 实战Asp.Net Core:DI生命周期

    title: 实战Asp.Net Core:DI生命周期 date: 2018-11-30 21:54:52 --- 1.前言 Asp.Net Core 默认支持 DI(依赖注入) 软件设计模式,那使 ...

  4. asp.net core利用DI实现自定义用户系统,脱离ControllerBase.User

    前言 很多时候其实我们并不需要asp.net core自带的那么复杂的用户系统,基于角色,各种概念,还得用EF Core,而且在web应用中都是把信息存储到cookie中进行通讯(我不喜欢放cooki ...

  5. Asp.net core 向Consul 注册服务

    Consul服务发现的使用方法:1. 在每台电脑上都以Client Mode的方式运行一个Consul代理, 这个代理只负责与Consul Cluster高效地交换最新注册信息(不参与Leader的选 ...

  6. Asp.Net Core Identity 完成注册登录

    Identity是Asp.Net Core全新的一个用户管理系统,它是一个完善的全面的庞大的框架,提供的功能有: 创建.查询.更改.删除账户信息 验证和授权 密码重置 双重身份认证 支持扩展登录,如微 ...

  7. 在Asp.Net Core中使用ModelConvention实现全局过滤器隔离

    从何说起 这来自于我把项目迁移到Asp.Net Core的过程中碰到一个问题.在一个web程序中同时包含了MVC和WebAPI,现在需要给WebAPI部分单独添加一个接口验证过滤器IActionFil ...

  8. ASP.NET Core中如影随形的”依赖注入”[下]: 历数依赖注入的N种玩法

    在对ASP.NET Core管道中关于依赖注入的两个核心对象(ServiceCollection和ServiceProvider)有了足够的认识之后,我们将关注的目光转移到编程层面.在ASP.NET ...

  9. 学习ASP.NET Core,怎能不了解请求处理管道[2]: 服务器在管道中的“龙头”地位

    ASP.NET Core管道由注册的服务器和一系列中间件构成.我们在上一篇中深入剖析了中间件,现在我们来了解一下服务器.服务器是ASP .NET Core管道的第一个节点,它负责完整请求的监听和接收, ...

随机推荐

  1. 用python写一个非常简单的QQ轰炸机

    闲的没事,就想写一个QQ轰炸机,按照我最初的想法,这程序要根据我输入的QQ号进行轰炸,网上搜了一下,发现网上的案列略复杂,就想着自己写一个算了.. 思路:所谓轰炸机,就是给某个人发很多信息,一直刷屏, ...

  2. 学习--->更新集合/内存/深浅拷贝

     一.计算机基础 1..软件(应用程序) 2.解释器/编译器 - 解释型语言:将代码每一行传递给计算机一行,常用编程语言python,PHP,Ruby. - 编译型语言:将代码完全执行完后会形成一个文 ...

  3. 关于height、offsetheight、clientheight、scrollheight、innerheight、outerheight的区别

    二.也是平时经常用到的offsetheight 它返回的高度是内容高+padding+边框,但是注意哦,木有加margin哦,当然一般也木有啥需要把margin加进去的,以上代码为例,结果显示上图h2 ...

  4. 浅谈MVC模式与MVVM模式的区别

    MVC模式: M:Model(数据模型),用于存放数据 V:View(视图),也就是用户界面 C:Controller是Model和View的协调者,Controller把Model中的数据拿过来给V ...

  5. 接口自动化集成到jenkins(Java+testng+maven+git)

    一jenkins启动命令:jenkins 查看端口号: 1.lsof -i:端口号 2.netstat -tunlp|grep 端口号 二: 登录:http://localhost:8080 输入:u ...

  6. 性能测试学习 第七课 --loadrunner中JavaVuser脚本的编写

    1.环境准备:      LoadRunner11----->对应JDK1.6版本(32位) LoadRunner12----->对应JDK1.7版本(32位) (一).JDK下载安装完成 ...

  7. oracle11G 用户密码180天修改概要文件过程

    oracle11G 用户密码180天修改概要文件过程 原因 创建用户的时候不指定概要文件的默认的概要文件是default, 而默认的概要文件中的设置如下,注意斜体部分 PROFILE RESOURCE ...

  8. gc笔记2

    空间分配担保:在发生MinorGC之前,虚拟机会检查老年代最大连续可用是否大于新生代所有对象的空间,如果这个条件成立,则minorgc时安全的

  9. Lesson 25 Do the English speak English?

    Text I arrived London at last. The railway station was big, black and dark. I did not know the way t ...

  10. Redis sortedset实现元素自动过期

    这里的自动过期,Redis并没有提供相应的api,但是可以使用一下方法来实现. 需求背景: 给用户返回的文章要求七日内不能重复:文章是存放在java list里边:(这一块就是从db将文章拿出来,然后 ...