You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过特性标记自动注册服务到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 21:13:14