轻量级.Net Core服务注册工具CodeDi发布啦

为什么做这么一个工具

因为我们的系统往往时面向接口编程的,所以在开发Asp .net core项目的时候,一定会有大量大接口及其对应的实现要在ConfigureService注册到ServiceCollection中,传统的做法是加了一个服务,我们就要注册一次(service.AddService()),又比如,当一个接口有多个实现,在构造函数中获取服务也不是很友好,而据我所知, .Net Core目前是没有什么自带的库或者方法解决这些问题,当然,如果引入第三方容器如AutoFac这些问题时能迎刃而解的,但是如何在不引入第三方容器来解决这个问题呢?
所以我就设计了这样的一个轻量级工具.

首先,放上该项目的Github地址(记得Star哦!!)

https://github.com/liuzhenyulive/CodeDi

CodeDi是一个基于 .Net Standard的工具库,它能帮助我们自动地在Asp .net core或者 .net core项目中完成服务的注册.

Overview

CodeDi 是 Code Dependency Injection的意思,在上次我在看了由依乐祝写的<.NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了>后,回想起我之前遇到的那些问题,感觉拨云见日,所以,我就开始着手写这个工具了.

如何使用CodeDi

安装Nuget包

CodeDi的Nuget包已经发布到了 nuget.org,您可以通过以下指令在您的项目中安装CodeDi

PM> Install-Package CodeDi

ConfigureServices中的配置

方法 1

您可以在StartupConfigureService方法中添加AddCodeDi完成对CodeDi的调用.服务的注册CodeDi会自动为您完成.

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCoreDi();
            services.AddMvc();
        }

方法 2

您也可以在AddCodeDi方法中传入一个Action<CodeDiOptions>参数,在这个action中,您可以对CodeDiOptions的属性进行配置.

       public void ConfigureServices(IServiceCollection services)
        {
            services.AddCoreDi(options =>
            {
                options.DefaultServiceLifetime = ServiceLifetime.Scoped;

            });
            services.AddMvc();
        }

方法 3

当然您也可以直接给AddCodeDi()方法直接传入一个CodeDiOptions实例.

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCoreDi(new CodeDiOptions()
            {
                DefaultServiceLifetime = ServiceLifetime.Scoped
            });
            services.AddMvc();
        }

你也可以在appsetting.json文件中配置CodeDiOptions的信息,并通过Configuration.Bind("CodeDiOptions", options)把配置信息绑定到一个CodeDiOptions实例.

appsetting.json file

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "CodeDiOptions": {
    "DefaultServiceLifetime": 1,
    "AssemblyNames": [
      "*CodeDi"
    ],
    "AssemblyPaths": [
      "C:\\MyBox\\Github\\CodeDI\\CodeDI\\bin\\Debug\\netstandard2.0"
    ],
    "IgnoreAssemblies": [
      "*Test"
    ],
    "IncludeSystemAssemblies": false,
    "IgnoreInterface": [
      "*Say"
    ],
    "InterfaceMappings": {
      "*Say": "*English"
    },
    "ServiceLifeTimeMappings": {
      "*Say": 0
    }
  }
}

ConfigureService方法

        public void ConfigureServices(IServiceCollection services)
        {
            var options=new CodeDiOptions();
            Configuration.Bind("CodeDiOptions", options);
            services.AddCoreDi(options);
            services.AddMvc();
        }

CodeDiOptions详解

属性名称 属性描述 数据类型 默认值
AssemblyPaths 在指定目录下加载Dll程序集 string[] Bin目录
AssemblyNames 选择要加载的程序集名称 (支持通配符) string[] *
IgnoreAssemblies 忽略的程序集名称 (支持通配符) string[] null
IncludeSystemAssemblies 是否包含系统程序集(当为false时,会忽略含有System,Microsoft,CppCodeProvider,WebMatrix,SMDiagnostics,Newtonsoft关键词和在App_Web,App_global目录下的程序集) bool false
IgnoreInterface 忽略的接口 (支持通配符) string[] null
InterfaceMappings 接口对应的服务 (支持通配符) ,当一个接口有多个实现时,如果不进行配置,则多个实现都会注册到SerciceCollection中 Dictionary<string, string> null
DefaultServiceLifetime 默认的服务生命周期 ServuceLifetime( Singleton,Scoped,Transient) ServiceLifetime.Scope
ServiceLifeTimeMappings 指定某个接口的服务生命周期,不指定为默认的生命周期 Dictionary<string, ServiceLifetime> null

InterfaceMappings

如果 ISay 接口有SayInChineseSayInEnglish 两个实现,我们只想把SayInEnglish注册到ServiceCollection

 public interface ISay
    {
        string Hello();
    }

      public class SayInChinese:ISay
    {
        public string Hello()
        {
            return "您好";
        }
    }

        public class SayInEnglish:ISay
    {
        public string Hello()
        {
            return "Hello";
        }
    }

那么我们可以这样配置InterfaceMappings.

options.InterfaceMappings=new Dictionary<string, string>(){{ "ISay", "SayInChinese" } }

也就是{接口名称(支持通配符),实现名称(支持通配符)}

ServiceLifeTimeMappings

如果我们希望ISay接口的服务的生命周期为Singleton,我们可以这样配置ServiceLifeTimeMappings.

options.ServiceLifeTimeMappings = new Dictionary<string, ServiceLifetime>(){{"*Say",ServiceLifetime.Singleton}};

也就是也就是{接口名称(支持通配符),Servicelifetime}

关于ServiceLifetime: https://github.com/aspnet/DependencyInjection/blob/master/src/DI.Abstractions/ServiceLifetime.cs

获取服务实例

当然, 您可以和之前一样,直接在构造函数中进行依赖的注入,但是当某个接口有多个实现而且都注册到了ServiceCollection中,获取就没有那么方便了,您可以用ICodeDiServiceProvider 来帮助您获取服务实例.

例如,当 ISay 接口有 SayInChineseSayInEnglish两个实现, 我们我们如何获取我们想要的服务实例呢?

 public interface ISay
    {
        string Hello();
    }

      public class SayInChinese:ISay
    {
        public string Hello()
        {
            return "您好";
        }
    }

        public class SayInEnglish:ISay
    {
        public string Hello()
        {
            return "Hello";
        }
    }
 public class HomeController : Controller
    {
        private readonly ISay _say;

        public HomeController(ICodeDiServiceProvider serviceProvider)
        {
            _say = serviceProvider.GetService<ISay>("*Chinese");
        }

        public string Index()
        {
            return _say.Hello();
        }
    }

ICodeDiServiceProvider.GetService<T>(string name=null)
参数中的Name支持通配符.

CodeDi如何实现的?

既然是一个轻量级工具,那么实现起来自然不会太复杂,我来说说比较核心的代码.

  private Dictionary<Type, List<Type>> GetInterfaceMapping(IList<Assembly> assemblies)
        {
            var mappings = new Dictionary<Type, List<Type>>();
            var allInterfaces = assemblies.SelectMany(u => u.GetTypes()).Where(u => u.IsInterface);
            foreach (var @interface in allInterfaces)
            {
                mappings.Add(@interface, assemblies.SelectMany(a =>
                        a.GetTypes().
                            Where(t =>
                                t.GetInterfaces().Contains(@interface)
                            )
                    )
                    .ToList());
            }
            return mappings;
        }

GetInterfaceMapping通过反射机制,首先获取程序集中的所有接口allInterfaces,然后遍历allInterfaces找到该接口对应的实现,最终,该方法返回接口和实现的匹配关系,为Dictionary<Type, List>类型的数据.

        private void AddToService(Dictionary<Type, List<Type>> interfaceMappings)
        {
            foreach (var mapping in interfaceMappings)
            {
                if (mapping.Key.FullName == null || (_options.IgnoreInterface != null &&
                   _options.IgnoreInterface.Any(i => mapping.Key.FullName.Matches(i))))
                    continue;

                if (mapping.Key.FullName != null && _options.InterfaceMappings != null &&
                    _options.InterfaceMappings.Any(u => mapping.Key.FullName.Matches(u.Key)))
                {
                    foreach (var item in mapping.Value.Where(value => value.FullName != null).
                        Where(value => value.FullName.Matches(_options.InterfaceMappings.FirstOrDefault(u => mapping.Key.FullName.Matches(u.Key)).Value)))
                    {
                        AddToService(mapping.Key, item);
                    }
                    continue;
                }

                foreach (var item in mapping.Value)
                {
                    AddToService(mapping.Key, item);
                }
            }
        }

该方法要判断CodeDiOptions中是否忽略了该接口,同时,是否指定实现映射关系.
什么叫实现映射关系呢?参见InterfaceMappings
如果指定了,那么就按指定的来实现,如果没指定,就会把每个实现都注册到ServiceCollection中.

        private readonly IServiceCollection _service;
        private readonly CodeDiOptions _options;
        private readonly ServiceDescriptor[] _addedService;

        public CodeDiService(IServiceCollection service, CodeDiOptions options)
        {
            _service = service ?? throw new ArgumentNullException(nameof(service));
            _options = options ?? new CodeDiOptions();
            _addedService = new ServiceDescriptor[service.Count];
            service.CopyTo(_addedService, 0);
            //在构造函数中,我们通过这种方式把Service中已经添加的服务读取出来
            //后面进行服务注册时,会进行判断,避免重复添加
        }

        private void AddToService(Type serviceType, Type implementationType)
        {
            ServiceLifetime serviceLifetime;
            try
            {
                serviceLifetime = _options.DefaultServiceLifetime;
                if (_options.ServiceLifeTimeMappings != null && serviceType.FullName != null)
                {
                    var lifeTimeMapping =
                        _options.ServiceLifeTimeMappings.FirstOrDefault(u => serviceType.FullName.Matches(u.Key));

                    serviceLifetime = lifeTimeMapping.Key != null ? lifeTimeMapping.Value : _options.DefaultServiceLifetime;

                }
            }
            catch
            {
                throw new Exception("Service Life Time Only Can be set in range of 0-2");
            }

            if (_addedService.Where(u => u.ServiceType == serviceType).Any(u => u.ImplementationType == implementationType))
                return;
            _service.Add(new ServiceDescriptor(serviceType, implementationType, serviceLifetime));
        }

AddToService中,要判断有没有对接口的生命周期进行配置,参见ServiceLifeTimeMappings,如果没有配置,就按DefaultServiceLifetime进行配置,DefaultServiceLifetime如果没有修改的情况下时ServiceLifetime.Scoped,即每个Request创建一个实例.

        private readonly IServiceProvider _serviceProvider;
        public CodeDiServiceProvider(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
        public T GetService<T>(string name) where T : class
        {
            return _serviceProvider.GetService<IEnumerable<T>>().FirstOrDefault(u => u.GetType().Name.Matches( name));
        }

这CodeDiServiceProvider的实现代码,这里参考了依乐祝写的<.NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了>给出的一种解决方案,即当某个接口注册了多个实现,其实可以通过IEnumerable获取所有的实现,CodeDiServiceProvider对其进行了封装.

Enjoy it

只要进行一次简单的CodeDi配置,以后系统中添加了新的接口以及对应的服务实现后,就不用再去一个个地Add到IServiceCollection中了.

如果有问题,欢迎Issue,欢迎PR.
最后,赏个Star呗! 前往Star

原文地址:https://www.cnblogs.com/CoderAyu/p/10269610.html

时间: 2024-11-10 14:20:47

轻量级.Net Core服务注册工具CodeDi发布啦的相关文章

Dubbo服务合买平台搭建出售发布之服务暴露&amp;心跳机制&amp;服务注册

Dubbo服务发布 Dubbo合买平台搭建出售 dsluntan.com Q:3393756370 VX:17061863513服务发布影响流程的主要包括三个部分,依次是: 服务暴露 心跳 服务注册 服务暴露是对外提供服务及暴露端口,以便消费端可以正常调通服务.心跳机制保证服务器端及客户端正常长连接的保持,服务注册是向注册中心注册服务暴露服务的过程. Dubbo服务暴露 此处只记录主要代码部分以便能快速定位到主要的核心代码: ServiceConfig.java中代码 if (registryU

.Net Core 商城微服务项目系列(二):使用Ocelot + Consul构建具备服务注册和发现功能的网关

1.服务注册 在上一篇的鉴权和登录服务中分别通过NuGet引用Consul这个包,同时新增AppBuilderExtensions类: public static class AppBuilderExtensions { public static IApplicationBuilder RegisterConsul(this IApplicationBuilder app,IApplicationLifetime lifetime,ServiceEntity serviceEntity) {

Spring Cloud Consul 实现服务注册和发现

Spring Cloud 是一个基于 Spring Boot 实现的云应用开发工具,它为基于 JVM 的云应用开发中涉及的配置管理.服务发现.断路器.智能路由.微代理.控制总线.全局锁.决策竞选.分布式会话和集群状态管理等操作提供了一种简单的开发方式.通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂.易部署和易维护的分布式系统开发工具包. Spring Cloud 包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Sprin

基于 Consul 实现 MagicOnion(GRpc) 服务注册与发现

0.简介 0.1 什么是 Consul Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置. 这里所谓的服务,不仅仅包括常用的 Api 这些服务,也包括软件开发过程当中所需要的诸如 Rpc.Redis.Mysql 等需要调用的资源. 简而言之 Consul 就是根据 Key/Value 存储了一套所有服务的 IP/Port 集合,当你 Grpc 客户端需要请求某种服务的时候,具体的 IP 与端口不需要你自己来进行指定,而是通过与 Consul Agent 通信

Nacos作为微服务注册中心,爱不释手的感觉

我觉得Nacos用起来还不错 在使用SpringCloud做分布式微服务架构时,注册中心是必不可少的一个组件.目前可以用的主要有:Eureka.Consul.Zookeeper.今天,我们就来说一下Alibaba的Nacos怎么样? 下载与安装 下载地址https://github.com/alibaba/nacos/releases 安装: Windows 下载解压后(.zip),直接点击bin/start.bat就可以了. Linux下载解压后(.tar.gz),同样,也是运行 bin/st

【架构】微服务实战:从发布到架构——上篇

微服务实战:从发布到架构——上篇  MaxLeap2016-03-23 10:42 “微服务”是当前软件架构领域非常热门的词汇,能找到很多关于微服务的定义.准则,以及如何从微服务中获益的文章,在企业的实践中去应用“微服务”的资源却很少.本篇文章中,会介绍微服务架构(Microservices Architecture)的基础概念,以及如何在实践中具体应用. 单体架构(Monolithic Architecture ) 企业级的应用一般都会面临各种各样的业务需求,而常见的方式是把大量功能堆积到同一

分布式服务注册和发现consul 简要介绍

Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其他分布式服务注册与发现的方案,Consul的方案更"一站式",内置了服务注册与发现框 架.分布一致性协议实现.健康检查.Key/Value存储.多数据中心方案,不再需要依赖其他工具(比如ZooKeeper等).使用起来也较 为简单.Consul用Golang实现,因此具有天然可移植性(支持Linux.windows和Mac OS X):安装包仅包含一个可执行文件,方便部署,与Docker等轻量级

【架构】微服务实战:从发布到架构——下篇

 MaxLeap2016-03-25 13:53 上篇文章介绍了微服务和单体架构的区别.微服务的设计.消息.服务间通信.数据去中心化,本篇会继续深入微服务,介绍其它特性. 治理去中心化 通常“治理”的意思是构建方案,并且迫使人们通过努力达到组织的目标.SOA治理指导开发者开发可重用的服务,以及随着时间推移,服务应该怎么被设计和开发.治理建立了服务提供者和消费者之间对于服务的协定,告诉消费者能从服务提供获取到什么样的支持. SOA中有两种常见的治理: 设计时的治理-定义和控制服务的创建.设计和服务

聊聊微服务的服务注册与发现

摘要: 一个好的服务注册发现中间件,应该是能完整地满足服务开发和治理的基础功能,然后才是性能和高可用.如果没有想清楚前面的功能,再高的可用性和性能都是浮云.最后,安全也同样重要.下面将从 服务注册.服务发现.容灾和高可用三个大方面来回答这些问题的主流做法. 引言 聊起微服务的服务注册与发现,很多人立马就会脱口而出 zk.etcd.consul.eureka 这些组件,进而聊到 CAP 如何取舍,性能如何,高可用和容灾是怎么实现的. 在这之前,站在组件使用者的角度,我想先问这么几个问题: 注册的