Scrutor 是什么,为什么不用原生 IServiceCollection 扩展?
Scrutor 是一个轻量级的第三方库,用来补足 .NET 原生
IServiceCollection在批量服务注册上的短板。原生 API(比如
AddTransient、
AddScoped)只支持单类型注册;而 Scrutor 提供了基于约定、泛型约束、程序集扫描等能力,让“自动发现并注册所有
IRepository<t></t>实现类”这类需求变得可行。
常见错误是直接用反射遍历类型然后硬调
AddTransient——容易漏掉泛型闭包、忽略生命周期一致性、无法按接口继承链匹配。Scrutor 内部做了这些判断,且 API 更语义化。
使用前需安装:
dotnet add package Scrutor
如何用 Scan() 批量注册同一程序集下的实现类?
这是最常用场景:把当前项目中所有实现了某个接口的类,按约定注册为对应生命周期服务。
services.Scan(scan => scan
.FromAssemblyOf<IRepository<int>>()
.AddClasses(classes => classes.AssignableTo<IRepository<object>>())
.AsImplementedInterfaces()
.WithTransientLifetime()
);
关键点说明:
FromAssemblyOf<...>()</...>指定扫描范围,推荐用一个标志性接口(而非随便选个类),避免因程序集加载顺序出错
AssignableTo<irepository>>()</irepository>匹配所有实现该接口(或其泛型变体)的类,包括
UserRepository : IRepository<user></user>
AsImplementedInterfaces()自动将类注册为其所有公开实现的接口(不包括基类接口),比手写
As<irepository>>()</irepository>更灵活
WithTransientLifetime()统一设为 Transient;也可用
WithScopedLifetime()或
WithSingletonLifetime()
注意:如果某类实现了多个接口(如
IRepository<t></t>和
IQueryHandler<t></t>),它会被注册多次——Scrutor 默认行为如此,不是 bug。
怎么排除测试类或内部类?
默认会扫到
internal类甚至
[ExcludeFromCodeCoverage]标记的类。必须显式过滤:
services.Scan(scan => scan
.FromAssemblyOf<IRepository<int>>()
.AddClasses(classes => classes
.AssignableTo<IRepository<object>>()
.Where(t => !t.IsAbstract && !t.IsGenericTypeDefinition))
.AsImplementedInterfaces()
.WithTransientLifetime()
);
重点过滤条件:
!t.IsAbstract:跳过抽象类(否则注册失败)
!t.IsGenericTypeDefinition:跳过像
RepositoryBase<t></t>这种未闭合的泛型定义(它们不能被实例化) 若需进一步排除命名空间,可用
.Where(t => !t.Namespace.StartsWith("Tests."))
注册时如何绑定泛型接口与泛型实现?
比如你有
interface IValidator<t> { }</t> 和 class UserValidator : IValidator<user> { }</user>,Scrutor 默认不会做泛型映射——它只认“完全匹配”的接口。
要支持泛型绑定,得用
AsClosedTypesOf():
services.Scan(scan => scan
.FromAssemblyOf<IValidator<int>>()
.AddClasses(classes => classes.AssignableTo<IValidator<object>>())
.AsClosedTypesOf(typeof(IValidator<>))
.WithTransientLifetime()
);
这个调用会让
UserValidator被注册为
IValidator<user></user>,而不是仅注册为
IValidator<object></object>或丢弃。
⚠️ 容易踩的坑:
AsClosedTypesOf()只支持“单个开放泛型定义”,不能同时匹配
IValidator和
IHandler如果实现类本身是泛型(如
class GenericValidator<t> : IValidator<t></t></t>),它仍会被注册,但实际解析时可能因构造函数参数缺失而失败——Scrutor 不校验可解析性,那是 DI 容器 runtime 的事
复杂点在于泛型约束和构造函数依赖的隐式耦合,Scrutor 做的是类型注册,不是依赖图验证。上线前务必跑一遍
ValidateScopes = true的集成测试。
