本文章介绍在 ASP.NET Core 中使用 Scrutor 来增强内置依赖注入框架
先决条件
.NET 8 SDK
介绍
Scrutor 并不是一个依赖注入框架,而是用来增强 ASP.NET Core 的内置依赖注入框架
在使用 ASP.NET Core 内置依赖注入框架注册服务时,只能一个一个地进行注册
无法像 Autofac 一样拥有强大的注册方式,而 Scrutor 主要的功能就是为了解决这一痛点
Scrutor 的卖点是它扫描程序集的方法,并自动注册它找到的类型
安装 Scrutor
通过命令行安装 Scrutor
dotnet add package Scrutor
复制代码
准备被扫描的程序集
创建了一个类库名为 Serivce,编译以后的就会生成 Service.dll 的程序集文件
其中有四个文件
#region IScanAssemblyMarker.cs 文件的代码
// 此接口用来查找程序集
public interface IScanAssemblyMarker { }
#endregion
#region OrderService.cs 文件的代码
public interface IOrderService { }
public class OrderService : IOrderService { }
#endregion
#region UserService.cs 文件的代码
public interface IUserService { Task<User> GetUserByIdAsync(int userId); }
public async Task<User> GetUserByIdAsync(int userId)
=> await Task.FromResult(new User { Id = userId, Name = $"UserName_{userId}" });
#endregion
#region InvoiceRepository.cs 文件的代码
public interface IInvoiceRepository { }
public class InvoiceRepository : IInvoiceRepository { }
#endregion
复制代码
使用 Scrutor 进行程序集扫描注册
直接上一个示例代码,该代码依赖于上面的 Serivce 类库
static void Main(string[] args)
{
var collection = new ServiceCollection();
collection.Scan(action =>
action.FromAssemblyOf<IScanAssemblyMarker>()
.AddClasses(@class => @class.Where(t => t.Name.EndsWith("Service")))
.UsingRegistrationStrategy(RegistrationStrategy.Throw)
.AsMatchingInterface()
.WithScopedLifetime()
.AddClasses(@class => @class.Where(t => t.Name.EndsWith("Repository")))
.UsingRegistrationStrategy(RegistrationStrategy.Throw)
.AsMatchingInterface()
.WithSingletonLifetime());
foreach (var item in collection)
{
var log = $"Service: {item.ServiceType.FullName} Lifetime: {item.Lifetime}\nInstance: {item.ImplementationType?.FullName}\n";
Console.WriteLine(log);
}
}
复制代码
在进行程序集扫描注册时,有五个步骤
找到程序集
选择并筛选要注册的服务
设置重复注册服务的替换策略
将实现注册为服务并指定策略
指定已注册类的生存期
找到程序集
在示例代码中,action.FromAssemblyOf<IScanAssemblyMarker>() 为找到 IScanAssemblyMarker 这个接口所在的程序集
找到程序集还有几个另外的扩展方法,如 FromAssemblyDependencies(Assembly assembly) 可以直接传入程序集
选择并筛选要注册的服务
在示例代码中,AddClasses(@class => @class.Where(t => t.Name.EndsWith("Service"))) 为选择并筛选要注册的服务,筛选出为 Service 字符结尾的类名
设置重复注册服务的替换策略
然后是 UsingRegistrationStrategy(RegistrationStrategy.Throw) ,设置重复注册服务的替换策略
这里 RegistrationStrategy.Throw 为 尝试注册现有服务时引发异常。
当然你可以设置为已经注册过的就直接选择跳过(RegistrationStrategy.Skip)或者附加(RegistrationStrategy.Append)
将实现注册为服务并指定策略
然后是示例代码中的 AsMatchingInterface() 将实现注册为服务并指定策略
这里我使用的 AsMatchingInterface() 相当于 services.AddSingleton<IClassName, ClassName>()
如果这里使用 AsSelf() 相当于 services.AddSingleton<ClassName>()
如果一个类实现了多个接口,可以使用 AsImplementedInterfaces()
这相当于:
services.AddSingleton<IClassName, ClassName>()
services.AddSingleton<IName, ClassName>()
指定已注册类的生存期
指定生命周期使用 WithSingletonLifetime() 为单例
指定生命周期使用 WithScopedLifetime() 为作用域
指定生命周期使用 WithTransientLifetime() 为瞬时
也可以使用 WithLifetime() 自定义
示例代码的执行结果
> Service: Service.IOrderService Lifetime: Scoped
> Instance: Service.OrderService
> Service: Service.IUserService Lifetime: Scoped
> Instance: Service.UserService
> Service: Service.IInvoiceRepository Lifetime: Singleton
> Instance: Service.InvoiceRepository
复制代码
使用 Scrutor 注册装饰器
在 Demo 中新增一个 实现接口 IUserService 的 CachedUserService 类
public class CachedUserService : IUserService
{
private readonly IUserService _repo;
private readonly ConcurrentDictionary<int, Task<User>> _dict;
public CachedUserService(IUserService repo)
{
_repo = repo;
_dict = new ConcurrentDictionary<int, Task<User>>();
}
// 先去 ConcurrentDictionary 键值对集合中查找缓存
public Task<User> GetUserByIdAsync(int userId)
=> _dict.GetOrAdd(userId, i => _repo.GetUserByIdAsync(userId));
}
复制代码
现在修改 Main 函数的代码为如下
static async Task Main(string[] args)
{
var collection = new ServiceCollection();
collection.Scan(action => action.FromAssemblyOf<IScanAssemblyMarker>()
.AddClasses(@class => @class.Where(t => t.Name.EndsWith("Service")))
.UsingRegistrationStrategy(RegistrationStrategy.Throw)
.AsMatchingInterface()
.WithSingletonLifetime()
.AddClasses(@class => @class.Where(t => t.Name.EndsWith("Repository")))
.UsingRegistrationStrategy(RegistrationStrategy.Throw)
.AsMatchingInterface()
.WithScopedLifetime());
// 给 已经注入的 IUserService 添加 Decorate
collection.Decorate<IUserService, CachedUserService>();
using (ServiceProvider root = collection.BuildServiceProvider())
using (IServiceScope scope = root.CreateScope())
{
IServiceProvider child = scope.ServiceProvider;
var blogRepository = child.GetRequiredService<IUserService>();
var blog1 = await blogRepository.GetUserByIdAsync(1);
var blog2 = await blogRepository.GetUserByIdAsync(1);
}
}
复制代码
在子容器中 连续运行 GetUserByIdAsync 方法时,第一次获取时还会经过装饰器时后由于键值对中没有缓存的值,将会继续调用 UserService.GetUserByIdAsync 去获取值,但是第二次调用时则直接拿取键值对中缓存的值
在 ASP.NET Core 中使用 Scrutor
示例
// This method gets called by the runtime.
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// 扫描程序集的方法,并自动注册找到的类型
services.Scan(action => action.FromAssemblyOf<IScanAssemblyMarker>()
.AddClasses(@class => @class.Where(t => t.Name.EndsWith("Service")))
.UsingRegistrationStrategy(RegistrationStrategy.Throw)
.AsMatchingInterface()
.WithScopedLifetime()
.AddClasses(@class => @class.Where(t => t.Name.EndsWith("Repository")))
.UsingRegistrationStrategy(RegistrationStrategy.Throw)
.AsMatchingInterface()
.WithScopedLifetime());
// 加上装饰器
services.Decorate<IUserService, CachedUserService>();
}
复制代码
参考文档
Using Scrutor to automatically register your services with the ASP.NET Core DI container
Adding decorated classes to the ASP.NET Core DI container using Scrutor
评论