IoC 自动注入:让依赖注册不再重复劳动

IoC 自动注入:让依赖注册不再重复劳动

解决方案goocz2025-06-15 17:22:573A+A-

在 ASP.NET Core 中,IoC(控制反转)功能通过依赖注入(DI)实现。ASP.NET Core 有一个内置的依赖注入容器,可以自动完成依赖注入。

我们可以结合反射、特性或程序集扫描来实现自动注册服务。


实现自动依赖注入的方法

需要做到两件事:

  1. 1. 通过程序集扫描注册服务
  2. 2. 按照接口与实现的命名规则注册服务

接口名:IRepository
实现类名:Repository
比如接口名为 IRepository,实现类名为 Repository

下面介绍两种实现方法。

方法一:通过特性标记实现自动注册

使用自定义特性来标记需要注册的类。

创建特性:

[AttributeUsage(AttributeTargets.Class)]
public class AutoInjectAttribute : Attribute
{
    public ServiceLifetime Lifetime { get; }

    public AutoInjectAttribute(ServiceLifetime lifetime = ServiceLifetime.Scoped)
    {
        Lifetime = lifetime;
    }
}

应用特性到类:

[AutoInject(ServiceLifetime.Scoped)]
public class Repository : IRepository
{
    // 类实现
}

注册代码:

public static class ServiceCollectionExtensions
{
    public static void AddAutoInjectServices(this IServiceCollection services, Assembly assembly)
    {
        var types = assembly.GetTypes();

        foreach (var type in types)
        {
            var autoInjectAttribute = type.GetCustomAttribute<AutoInjectAttribute>();
            if (autoInjectAttribute != null)
            {
                var interfaces = type.GetInterfaces();
                foreach (var @interface in interfaces)
                {
                    if (@interface.Name == "I" + type.Name)
                    {
                           Console.WriteLine(#34;注册服务:{@interface.Name} -> {type.Name},生命周期:{attribute.Lifetime}");
                       switch (autoInjectAttribute.Lifetime)
                        {
                            case ServiceLifetime.Singleton:
                                services.AddSingleton(@interface, type);
                                break;
                            case ServiceLifetime.Scoped:
                                services.AddScoped(@interface, type);
                                break;
                            case ServiceLifetime.Transient:
                                services.AddTransient(@interface, type);
                                break;
                        }
                    }
                }
            }
        }
    }
}

使用方式:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAutoInjectServices(Assembly.GetExecutingAssembly());
    }
}

这样就可以通过特性控制哪些类需要注册,并指定生命周期。

方法二:按命名规则自动注册

可以通过类名和接口名的命名规则来自动注册服务。

注册代码:

public static class ServiceCollectionExtensions
{
    public static void AddAutoInjection(this IServiceCollection services, Assembly assembly)
    {
        var types = assembly.GetTypes();

        foreach (var type in types)
        {
            var interfaces = type.GetInterfaces();

            foreach (var @interface in interfaces)
            {
                if (@interface.Name == "I" + type.Name)
                {
                    services.AddScoped(@interface, type);
                }
            }
        }
    }
}

使用方式:

Startup.csProgram.cs 中调用方法:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAutoInjection(Assembly.GetExecutingAssembly());
    }
}

这样就可以自动注册当前程序集中符合命名规则的服务。

优化

建议

  • o 支持多个程序集扫描;
  • o 添加配置选项,灵活控制注册行为;
  • o 增加单元测试验证注册逻辑是否正确。
  • o 注册过程添加日志,利于调试。

特性注册

  • o 重复扫描类型:每次调用都重新获取程序集类型,建议缓存。
  • o 只能识别 I+类名 接口,建议支持更多命名格式。
  • o 特性注册不支持泛型接口:如 IRepository<T> 无法识别。
public static class ServiceCollectionExtensions
{
    private static readonly ConcurrentDictionary<Assembly, Type[]> _cachedTypes = new();

    public static void AddAutoInjectServices(this IServiceCollection services, Assembly assembly)
    {
        if (!_cachedTypes.TryGetValue(assembly, out var types))
        {
            types = assembly.GetTypes();
            _cachedTypes.TryAdd(assembly, types);
        }

        foreach (var type in types)
        {
            try
            {
                if (!type.IsClass || type.IsAbstract || type.IsGenericType)
                    continue;

                var attribute = type.GetCustomAttribute<AutoInjectAttribute>();
                if (attribute == null)
                    continue;

                var interfaces = type.GetInterfaces();

                foreach (var @interface in interfaces)
                {
                    if (@interface.Name != "I" + type.Name && 
                        !@interface.Name.StartsWith("I") && 
                        !@interface.Name.EndsWith("Service"))
                        continue;

                    switch (attribute.Lifetime)
                    {
                        case ServiceLifetime.Singleton:
                            services.AddSingleton(@interface, type);
                            break;
                        case ServiceLifetime.Scoped:
                            services.AddScoped(@interface, type);
                            break;
                        case ServiceLifetime.Transient:
                            services.AddTransient(@interface, type);
                            break;
                    }

                    Console.WriteLine(#34;注册服务:{@interface.Name} -> {type.Name},生命周期:{attribute.Lifetime}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(#34;注册失败: {type.FullName},错误:{ex.Message}");
            }
        }
    }
}

命名规则注册

  • o 只支持 Scoped 生命周期,应支持 Singleton/Transient。
  • o 没有过滤机制:可能误注册不需要的服务。
public static void AddAutoInjection(this IServiceCollection services, Assembly assembly)
{
    var types = assembly.GetTypes();

    foreach (var type in types)
    {
        try
        {
            if (type.IsClass && !type.IsAbstract && !type.IsGenericType)
            {
                var interfaces = type.GetInterfaces();

                foreach (var @interface in interfaces)
                {
                    if (@interface.Name == "I" + type.Name && @interface.IsAssignableFrom(type))
                    {
                        services.AddScoped(@interface, type);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(#34;注册 {type.FullName} 失败:{ex.Message}");
        }
    }
}
点击这里复制本文地址 以上内容由goocz整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

果子教程网 © All Rights Reserved.  蜀ICP备2024111239号-5