如何通过特性标记自动注册服务到DI容器?求替代手动注册方案
.NET DI 服务自动注册实现方案
一、自定义特性+反射扫描方案
1. 定义服务标记特性
先创建一个特性类,用来标记需要自动注册的服务,并指定生命周期:
[AttributeUsage(AttributeTargets.Class, Inherited = false)] public class ServiceAttribute : Attribute { public ServiceLifetime Lifetime { get; } public ServiceAttribute(ServiceLifetime lifetime = ServiceLifetime.Scoped) { Lifetime = lifetime; } }
2. 给服务类添加特性标记
在需要注册的服务类上加上该特性,指定生命周期:
[Service(ServiceLifetime.Scoped)] public class CategoryService : ICategoryService { // 服务实现代码 } [Service(ServiceLifetime.Singleton)] public class ProductCacheService : IProductCacheService { // 服务实现代码 }
3. 编写自动注册扩展方法
通过反射扫描程序集中带特性标记的类,自动匹配对应的接口并注册到DI容器:
public static class ServiceCollectionExtensions { public static IServiceCollection AddAutoRegisteredServices(this IServiceCollection services, Assembly targetAssembly) { // 筛选出所有带ServiceAttribute的非抽象类 var serviceClasses = targetAssembly.GetTypes() .Where(t => t.IsClass && !t.IsAbstract && t.GetCustomAttribute<ServiceAttribute>() != null); foreach (var serviceClass in serviceClasses) { // 匹配命名为"I+类名"的接口(比如CategoryService对应ICategoryService) var matchingInterface = serviceClass.GetInterfaces() .FirstOrDefault(i => i.Name == $"I{serviceClass.Name}"); if (matchingInterface == null) continue; // 根据特性指定的生命周期注册服务 var serviceAttr = serviceClass.GetCustomAttribute<ServiceAttribute>()!; switch (serviceAttr.Lifetime) { case ServiceLifetime.Scoped: services.AddScoped(matchingInterface, serviceClass); break; case ServiceLifetime.Singleton: services.AddSingleton(matchingInterface, serviceClass); break; case ServiceLifetime.Transient: services.AddTransient(matchingInterface, serviceClass); break; } } return services; } }
4. 在启动类中调用扩展方法
在Program.cs中调用扩展方法,传入服务所在的程序集:
// 传入包含服务类的程序集,这里以CategoryService所在程序集为例 var serviceAssembly = typeof(CategoryService).Assembly; builder.Services.AddAutoRegisteredServices(serviceAssembly);
二、更轻量的约定式注册(无需特性)
如果你的服务类和接口遵循类名=接口名去掉前缀I的命名约定(比如ProductService实现IProductService),可以不用特性,直接按约定扫描注册:
public static class ServiceCollectionExtensions { public static IServiceCollection AddConventionalServices(this IServiceCollection services, Assembly targetAssembly) { // 筛选出所有以"Service"结尾的非抽象类 var serviceClasses = targetAssembly.GetTypes() .Where(t => t.IsClass && !t.IsAbstract && t.Name.EndsWith("Service")); foreach (var serviceClass in serviceClasses) { var matchingInterface = serviceClass.GetInterfaces() .FirstOrDefault(i => i.Name == $"I{serviceClass.Name}"); if (matchingInterface != null) { // 默认注册为Scoped,可根据需求修改为其他生命周期 services.AddScoped(matchingInterface, serviceClass); } } return services; } }
调用方式和之前一致:
builder.Services.AddConventionalServices(typeof(CategoryService).Assembly);
注意事项
- 若一个服务类实现多个接口,上述代码只会注册匹配
I+类名的接口;如需注册所有接口,可修改逻辑遍历所有接口进行注册 - 扫描程序集时要确保传入的是包含服务类的正确程序集,避免漏扫
- 可根据项目需求扩展逻辑,比如只扫描特定命名空间下的服务类
内容的提问来源于stack exchange,提问作者JacksonT
相关产品推荐
相关产品推荐

