.Net Core 优雅的实现依赖注入(Dependency Injection)(原生)

.Net Core 原生支持依赖注入(Dependency Injection)

构造注入

一般在Service中通过加入Add…将服务加入DI构造容器中,如下代码:

    public static class Programm
    {
        public static void Main(string[] args)
        {
            CreateDefaultHostBuider(args).Build().Run();
        }

        private static IHostBuilder CreateDefaultHostBuider(string[] args)
        {
            return Host.CreateDefaultBuilder(args)
                .ConfigureServices((context, services) =>
                services.AddLogging()
                        .AddTransient<IAspectLoadService, AspectLoadService>() // Service Injection
                        .AddHostedService<Start>()
                );
        }
    }

看下接口IAspectLoadService以及它的实现IAspectLoadService。

    public interface IAspectLoadService
    {
        IAspect Load(IAspectReference reference);
    }
    public class AspectLoadService : IAspectLoadService
    {
        public IAspect Load(IAspectReference reference)
        {
            return new Aspect(reference, "TestAspect");
        }
    }

所以在Start中我们需要使用AspectLoadService时,只要将接口IAspectLoadService放入构造函数中,如下:

    public class Start : BackgroundService
    {
        private readonly IAspectLoadService _aspectLoadService;

        public Start(IAspectLoadService aspectLoadService) // 构造函数注入
        {
            _aspectLoadService = aspectLoadService;
        }

        protected override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _aspectLoadService.Load(new AspectReference());

            return Task.CompletedTask;
        }
    }

不足之处

以上方法可以使得DI在项目中使用起来,但是每次添加新的服务,需要在ConfigureServices中注册。这样的实现很容易出现,

  • 忘记注册
  • 在多人开发团队中,很有可能同时修改注册代码导致冲突。
  • 如果注册服务较多,不利于代码阅读和整洁。

改进目标

我们希望服务是否可被注入是由服务自身决定的,这样就可以解决上述问题了。

改进思路

采用反射

可以通过给服务加注标注就可以决定改服务是否可以被注入,如下,

    [ServiceLocate(typeof(IAspectLoadService))] // 给Service标注是否可以被注入
    public class AspectLoadService : IAspectLoadService
    {
        public IAspect Load(IAspectReference reference)
        {
            return new Aspect(reference, "TestAspect");
        }
    }

这样就可以避免开发之间的影响,因为只关注自己开发的服务了。

在注册服务的时候统一查看ServiceLocate的标注,然后统一注入容器,如下,

    public static class Programm
    {
        public static void Main(string[] args)
        {
            CreateDefaultHostBuider(args).Build().Run();
        }

        private static IHostBuilder CreateDefaultHostBuider(string[] args)
        {
            return Host.CreateDefaultBuilder(args)
                .ConfigureServices((context, services) =>
                services.AddLogging()
                        .RegisterDomain("TestCommon") // 统一查看ServiceLocate标注,然后注册
                        .AddHostedService<Start>()
                );
        }
    }

这里的"TestComon"是要查看的程序集的名称。

具体实现

先看下ServiceLocate,该Attribute需要两个参数:服务的接口以及注册服务的类型,默认为Transient

    public class ServiceLocateAttribute : Attribute
    {
        private Type _iService;
        private ServiceType _serviceType;

        public ServiceLocateAttribute(Type iService, ServiceType type = ServiceType.Transient)
        {
            _iService = iService;
            _serviceType = type;
        }

        public Type IService { get => _iService; set => _iService = value; }

        public ServiceType ServiceType { get => _serviceType; set => _serviceType = value; }
    }

再看下方法RegisterDomain,

        public static IServiceCollection RegisterDomain(this IServiceCollection services, params string[] domains)
        {
            var domainList = domains.Append("Common.Core").ToList();

            var asms = domainList.Select(domain => Assembly.Load(domain)).ToList();

            var impls = asms.SelectMany(asm => asm.GetExportedTypes().Where(t => !t.IsInterface).ToList()).ToList();

            foreach (var impl in impls)
            {
                var serviceLocateObject = impl.GetCustomAttribute(typeof(ServiceLocateAttribute));
                if (serviceLocateObject != null)
                {
                    var iFace = (serviceLocateObject as ServiceLocateAttribute).IService;
                    var serviceType = (serviceLocateObject as ServiceLocateAttribute).ServiceType;
                    switch (serviceType)
                    {
                        case ServiceType.Scoped:
                            services.AddScoped(iFace, impl);
                            break;
                        case ServiceType.Singleton:
                            services.AddSingleton(iFace, impl);
                            break;
                        default:
                            services.AddTransient(iFace, impl);
                            break;
                    }                    
                }
            }

            return services;
        }

以下是添加默认程序集(自定义)

var domainList = domains.Append(“Common.Core”).ToList();

通过反射获取所有需要查看的程序集

var asms = domainList.Select(domain => Assembly.Load(domain)).ToList();

获取所有程序集中的实现,

var impls = asms.SelectMany(asm => asm.GetExportedTypes().Where(t => !t.IsInterface).ToList()).ToList();

然后进入循环检查和服务注入,

            foreach (var impl in impls)
            {
                var serviceLocateObject = impl.GetCustomAttribute(typeof(ServiceLocateAttribute));
                if (serviceLocateObject != null)
                {
                    var iFace = (serviceLocateObject as ServiceLocateAttribute).IService;
                    var serviceType = (serviceLocateObject as ServiceLocateAttribute).ServiceType;
                    switch (serviceType)
                    {
                        case ServiceType.Scoped:
                            services.AddScoped(iFace, impl);
                            break;
                        case ServiceType.Singleton:
                            services.AddSingleton(iFace, impl);
                            break;
                        default:
                            services.AddTransient(iFace, impl);
                            break;
                    }                    
                }
            }

参考代码

RegisterDomain

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值