在C#的.NET应用开发中,依赖注入是构建松耦合系统的核心机制,常规的服务注册需要手动为每个实现类指定对应的服务类型和生命周期,当项目中的服务数量达到数十甚至上百个时,这种逐个注册的方式会显得非常低效,还容易出现注册遗漏的问题。Scrutor是一个专门优化依赖注入注册流程的第三方库,它支持通过配置规则批量扫描程序集,自动完成服务的匹配和注册,能够很好地解决手动注册的痛点。

Scrutor基础介绍
Scrutor是基于Microsoft.Extensions.DependencyInjection扩展的轻量级库,它的核心能力是提供IServiceCollection的扩展方法,让开发者可以通过扫描指定程序集,按照自定义规则批量注册服务。它不会替换原有的依赖注入容器,只是在原有基础上增加了批量注册的能力,因此和现有的依赖注入用法完全兼容。
使用前需要先通过NuGet安装Scrutor包,安装命令如下:
dotnet add package Scrutor
基础批量注册示例
假设我们有如下的服务接口和实现类,分布在同一个程序集中:
// 服务接口
public interface IUserService
{
string GetUserName(int id);
}
public interface IOrderService
{
string GetOrderInfo(int orderId);
}
// 服务实现类
public class UserService : IUserService
{
public string GetUserName(int id)
{
return $"用户{id}";
}
}
public class OrderService : IOrderService
{
public string GetOrderInfo(int orderId)
{
return $"订单{orderId}";
}
}
使用Scrutor批量注册这些服务的代码如下:
using Microsoft.Extensions.DependencyInjection;
using Scrutor;
var services = new ServiceCollection();
// 扫描当前程序集,批量注册服务
services.Scan(scan => scan
// 从当前执行的程序集开始扫描
.FromAssemblyOf<IUserService>()
// 筛选要注册的类型:类是公开的、非抽象的
.AddClasses(classes => classes.Where(type => type.Name.EndsWith("Service")))
// 将类注册为所有它实现的接口
.AsImplementedInterfaces()
// 设置服务生命周期为瞬时
.WithTransientLifetime());
var serviceProvider = services.BuildServiceProvider();
var userService = serviceProvider.GetRequiredService<IUserService>();
Console.WriteLine(userService.GetUserName(1));
常用配置规则说明
程序集选择
Scrutor支持多种程序集选择方式,常用的有:
FromAssemblyOf<T>():从包含类型T的程序集开始扫描FromAssemblies(params Assembly[] assemblies):指定多个具体程序集扫描FromAssemblyDependencies(Assembly assembly):扫描指定程序集及其依赖的所有程序集
类型筛选
通过AddClasses方法可以筛选要注册的类型,支持多种筛选条件:
// 注册所有公开的非抽象类
.AddClasses(classes => classes.Public())
// 注册指定命名空间下的类
.AddClasses(classes => classes.InNamespaces("MyApp.Services"))
// 注册带有指定特性的类
.AddClasses(classes => classes.WithAttribute<MyCustomAttribute>())
服务映射规则
注册类型后需要指定服务类型的映射规则,常用的有:
AsSelf():将类型注册为自身,即services.AddTransient<UserService>()AsImplementedInterfaces():注册为类型实现的所有接口As<TService>():注册为指定的服务类型AsMatchingInterface():按照名称匹配接口,比如UserService会匹配IUserService
生命周期配置
Scrutor支持三种标准的依赖注入生命周期:
WithTransientLifetime():瞬时生命周期,每次获取都创建新实例WithScopedLifetime():作用域生命周期,同一作用域内获取同一实例WithSingletonLifetime():单例生命周期,全局唯一实例
高级用法示例
按条件设置不同生命周期
如果有部分服务需要单例,部分需要瞬时,可以通过条件判断分别配置:
services.Scan(scan => scan
.FromAssemblyOf<IUserService>()
.AddClasses(classes => classes.Where(type => type.Name.EndsWith("Service")))
.AsImplementedInterfaces()
.WithLifetime(lifetimeSelector =>
lifetimeSelector.If(type => type.Name.EndsWith("CacheService"),
lifetime => lifetime.WithSingletonLifetime())
.Else(lifetime => lifetime.WithTransientLifetime())));
排除指定类型
如果不想注册某些特定类型,可以在筛选时排除:
services.Scan(scan => scan
.FromAssemblyOf<IUserService>()
.AddClasses(classes => classes
.Where(type => type.Name.EndsWith("Service"))
// 排除测试用的服务类
.Where(type => !type.Name.Contains("Test")))
.AsImplementedInterfaces()
.WithTransientLifetime());
注意事项
使用Scrutor批量注册时需要注意几个问题:首先,扫描的程序集范围不要过大,避免扫描到不需要的程序集导致注册冗余服务;其次,如果同一个服务类型有多个实现,批量注册会全部注册,获取时如果需要指定实现需要额外配置;最后,Scrutor的注册规则和手动注册不冲突,两者可以混合使用,手动注册的优先级高于批量注册的结果。
通过Scrutor的批量扫描和自动注册能力,开发者可以将原本需要数十行甚至上百行的服务注册代码简化成几行配置,大幅降低维护成本,尤其适合中大型项目的依赖注入管理。