在现代 .NET 开发中,依赖注入(DI)已经成为标配功能。手动注册每一个服务,不仅代码繁琐、重复度高,还容易出现漏注册、错注册的问题,给项目维护带来负担。Scrutor 是一款轻量的 NuGet 扩展库,基于约定优于配置的思想,让 .NET 依赖注入实现全自动注册,大幅简化开发流程。
本文结合实际项目经验,详解 Scrutor 的核心用法、场景适配和最佳实践,帮你快速落地自动化 DI。
一、什么是 Scrutor
Scrutor 由开发者 Kristian Hellang 打造,是 ASP.NET Core 原生 DI 容器的扩展库。它不改变原生 DI 的核心逻辑,只提供程序集扫描、批量筛选、自动注册的能力,让服务注册从手动编写变成自动匹配。
核心优势
告别重复代码,统一注册规则,降低维护成本 严格遵循 DRY 原则,保证全项目服务注册逻辑一致 完美支持 Scoped/Transient/Singleton 三种生命周期 过滤、命名、继承、泛型等灵活配置,适配各类项目结构 无侵入式设计,兼容原生 DI,可随时切换回手动注册
二、首选推荐:标记接口模式(企业级最佳实践)
这是我在实际项目中长期使用的方案,简洁、易维护、可读性强,也是中大型项目的最优选择。
2.1 定义生命周期标记接口
先创建三个空接口,仅用于标记服务的生命周期,无任何业务逻辑:
/// <summary>
/// Scoped 生命周期标记接口
/// </summary>
publicinterfaceIScopedDependency { }
/// <summary>
/// Transient 生命周期标记接口
/// </summary>
publicinterfaceITransientDependency { }
/// <summary>
/// Singleton 生命周期标记接口
/// </summary>
publicinterfaceISingletonDependency { }
2.2 封装统一自动注入配置
写一个扩展方法,一次性完成所有标记服务的扫描注册,后续无需修改:
public staticclassDependencyInjectionExtensions
{
public static IServiceCollection AddAutoDependencyInjection(this IServiceCollection services)
{
// 扫描项目所有程序集,自动匹配标记接口的服务
services.Scan(scan => scan
.FromApplicationDependencies()
// 注册 Scoped 服务
.AddClasses(classes => classes.AssignableTo<IScopedDependency>())
.AsImplementedInterfaces()
.WithScopedLifetime()
// 注册 Transient 服务
.AddClasses(classes => classes.AssignableTo<ITransientDependency>())
.AsImplementedInterfaces()
.WithTransientLifetime()
// 注册 Singleton 服务
.AddClasses(classes => classes.AssignableTo<ISingletonDependency>())
.AsImplementedInterfaces()
.WithSingletonLifetime()
);
return services;
}
}
2.3 极简使用方式
新增服务时,只需要实现对应的标记接口,无需编写任何注册代码:
// 业务接口继承 IScopedDependency,标记生命周期
publicinterfaceIUserService : IScopedDependency
{
Task<UserInfo> GetUserByIdAsync(Guid userId);
}
// 实现类无需额外配置
publicclassUserService : IUserService
{
public Task<UserInfo> GetUserByIdAsync(Guid userId)
{
// 业务实现
return Task.FromResult(new UserInfo());
}
}
推荐理由
零配置:新增服务只加一个接口,完全不用管注册逻辑 高可读:一眼就能看出服务的生命周期,代码自解释 强类型:编译期检查,避免运行时注册错误 易维护:全项目统一规则,新人上手无成本
三、常用实用用法
除了标记接口,Scrutor 还支持多种扫描规则,适配不同项目场景。
3.1 基于命名约定注册
适合命名规范严格的现有项目,按类名后缀批量注册:
services.Scan(scan => scan
.FromAssemblyOf<Program>()
// 自动注册所有以 Service 结尾的类
.AddClasses(classes => classes.Where(type => type.Name.EndsWith("Service")))
.AsImplementedInterfaces()
.WithScopedLifetime()
// 自动注册所有以 Repository 结尾的仓储类
.AddClasses(classes => classes.Where(type => type.Name.EndsWith("Repository")))
.AsImplementedInterfaces()
.WithScopedLifetime()
);
3.2 基于命名空间注册
适合按模块分层的项目,按命名空间批量注册:
services.Scan(scan => scan
.FromAssemblyOf<Program>()
// 注册应用服务层
.AddClasses(classes => classes.InNamespaces("MyProject.Application.Services"))
.AsImplementedInterfaces()
.WithScopedLifetime()
// 注册数据仓储层
.AddClasses(classes => classes.InNamespaces("MyProject.Infrastructure.Repositories"))
.AsImplementedInterfaces()
.WithScopedLifetime()
);
3.3 基于基类注册
适合封装通用基类的项目,批量注册继承基类的所有子类:
// 抽象基类,封装通用日志、工具方法
publicabstractclassBaseService
{
protectedreadonly ILogger<BaseService> _logger;
protected BaseService(ILogger<BaseService> logger) => _logger = logger;
}
// 自动扫描注册所有继承 BaseService 的类
services.Scan(scan => scan
.FromAssemblyOf<BaseService>()
.AddClasses(classes => classes.InheritedFrom<BaseService>())
.AsSelf()
.WithScopedLifetime()
);
3.4 注册为自身类型
适合CQRS 命令/查询模式,直接注册实现类本身:
services.Scan(scan => scan
.FromAssemblyOf<Program>()
.AddClasses(classes => classes.AssignableTo<ICommandHandler>())
.AsSelf() // 不注册接口,直接注册类本身
.WithTransientLifetime()
);
// 使用时直接获取实现类
var handler = _serviceProvider.GetRequiredService<CreateUserCommandHandler>();
3.5 自定义注册策略
控制重复服务的注册行为,避免冲突:
services.Scan(scan => scan
.FromAssemblyOf<Program>()
.AddClasses(classes => classes.AssignableTo<IValidator>())
.UsingRegistrationStrategy(RegistrationStrategy.Skip) // 跳过已存在的注册
.AsImplementedInterfaces()
.WithTransientLifetime()
);
可选策略:
Append:追加注册(默认)Skip:跳过已注册服务Replace:替换已注册服务
四、高级进阶用法
4.1 装饰器模式
Scrutor 原生支持装饰器,轻松实现日志、缓存、事务等横切逻辑:
// 核心服务 + 装饰器都实现同一接口
publicinterfaceIOrderService { void CreateOrder(); }
publicclassOrderService : IOrderService { }
publicclassLoggingOrderDecorator : IOrderService { }
// 先注册核心服务
services.Scan(scan => scan.FromAssemblyOf<IOrderService>().AddClasses().AsImplementedInterfaces().WithScopedLifetime());
// 叠加装饰器(执行顺序:后注册的先执行)
services.Decorate<IOrderService, LoggingOrderDecorator>();
4.2 条件过滤注册
通过特性标记,跳过不需要自动注册的服务:
// 自定义跳过特性
[AttributeUsage(AttributeTargets.Class)]
publicclassSkipAutoRegistrationAttribute : Attribute { }
// 扫描时排除标记类
services.Scan(scan => scan
.FromApplicationDependencies()
.AddClasses(classes => classes
.AssignableTo<IScopedDependency>()
.Where(t => !t.HasAttribute<SkipAutoRegistrationAttribute>())
)
.AsImplementedInterfaces()
.WithScopedLifetime()
);
// 使用:标记无需自动注册的类
[SkipAutoRegistration]
publicclassTestService : IScopedDependency { }
4.3 批量注册泛型服务
适配仓储模式等泛型接口,自动注册封闭泛型:
services.Scan(scan => scan
.FromAssemblyOf<IRepository<>>()
.AddClasses(classes => classes.AssignableTo(typeof(IRepository<>)))
.AsClosedTypesOf(typeof(IRepository<>))
.WithScopedLifetime()
);
// 直接使用
var userRepo = _serviceProvider.GetRequiredService<IRepository<User>>();
五、各注册方式对比
标记接口
六、落地最佳实践
6.1 分层扫描注册
按项目架构分层注册,职责清晰,便于排查问题:
// 应用层注册
public static IServiceCollection AddApplicationLayer(this IServiceCollection services)
{
services.Scan(scan => scan
.FromAssemblyOf<ApplicationLayerMarker>()
.AddClasses(classes => classes.AssignableTo<IScopedDependency>())
.AsImplementedInterfaces()
.WithScopedLifetime()
);
return services;
}
// 基础设施层注册
public static IServiceCollection AddInfrastructureLayer(this IServiceCollection services)
{
services.Scan(scan => scan
.FromAssemblyOf<InfrastructureLayerMarker>()
.AddClasses(classes => classes.AssignableTo<IRepository>())
.AsImplementedInterfaces()
.WithScopedLifetime()
);
return services;
}
6.2 自动+手动注册配合
自动注册通用服务,手动注册特殊配置服务:
// 批量自动注册
services.AddAutoDependencyInjection();
// 手动注册特殊服务(带自定义配置)
services.AddScoped<IPaymentService>(sp =>
{
var config = sp.GetRequiredService<IOptions<PaymentConfig>>();
return new AlipayPaymentService(config.Value.AppId);
});
6.3 测试环境适配
单元测试中,直接用 Mock 覆盖自动注册的服务:
// 测试项目重写注册
services.AddScoped<IUserService, MockUserService>();
services.AddScoped<IOrderService, MockOrderService>();
七、总结
Scrutor 让 .NET 依赖注入从手动编码变成自动匹配,不同注册方式对应不同项目场景:
新项目搭建 → 标记接口模式(首选) 老项目迁移 → 命名约定模式 模块化架构 → 命名空间模式 CQRS 架构 → 注册自身模式
标记接口模式是最推荐的方案,它兼顾简洁性、可读性和可维护性,能显著提升开发效率,降低团队协作成本,是企业级 .NET 项目的标配实践。