scrutor 是补足 .net 原生 iservicecollection 批量注册短板的轻量级库,支持基于约定、泛型约束和程序集扫描的自动服务注册,避免手动反射导致的泛型闭包遗漏、生命周期不一致等问题。

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 的集成测试。