为什么做这么一个工具

因为我们的系统往往时面向接口编程的,所以在开发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

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

  1. .NET Core 服务诊断工具

    前言: 前一篇文中介绍了.NET Core-全局性能诊断工具 的使用方法,那么接下来自己实现一个简单.NET Core的诊断工具. 该工具主要包括:.NET Core 程序进程信息查看.性能计数器结果 ...

  2. .net core 服务注册生命周期

    在Asp.Net core中的IServiceCollection容器中注册服务的生命周期分以下3种: 1.Transient 通过AddTransient注册,会在IServiceCollectio ...

  3. spring-cloud-eureka服务注册与发现

    Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的.SpringCloud将它集成在其子项 ...

  4. .net core grpc consul 实现服务注册 服务发现 负载均衡(二)

    在上一篇 .net core grpc 实现通信(一) 中,我们实现的grpc通信在.net core中的可行性,但要在微服务中真正使用,还缺少 服务注册,服务发现及负载均衡等,本篇我们将在 .net ...

  5. .Net Core Grpc Consul 实现服务注册 服务发现 负载均衡

    本文是基于..net core grpc consul 实现服务注册 服务发现 负载均衡(二)的,很多内容是直接复制过来的,..net core grpc consul 实现服务注册 服务发现 负载均 ...

  6. spring cloud+.net core搭建微服务架构:服务注册(一)

    背景 公司去年开始使用dotnet core开发项目.公司的总体架构采用的是微服务,那时候由于对微服务的理解并不是太深,加上各种组件的不成熟,只是把项目的各个功能通过业务层面拆分,然后通过nginx代 ...

  7. ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 03. 服务注册和管道

    ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 03. 服务注册和管道 语雀: https://www.yuque.com/yuejiangliu/dotnet/ ...

  8. 温故知新,.Net Core遇见Consul(HashiCorp),实践分布式服务注册与发现

    什么是Consul 参考 https://www.consul.io https://www.hashicorp.com 使用Consul做服务发现的若干姿势 ASP.NET Core 发布之后通过命 ...

  9. 我是服务的执政官-服务发现和注册工具consul简介

    服务发现和注册 我们有了两个服务.服务A的IP地址是192.168.0.1,端口9001,服务B的IP地址192.168.0.2,端口9002.我们的客户端需要调用服务A和服务B,我们只需要在配置文件 ...

随机推荐

  1. sniffer 简介

    http://www.doc88.com/p-095375416629.html 介绍sniffer的工作原理及简单介绍.

  2. Robot framework之元素定位实战

    1.1  id 和name 定位 Web页面都是由许多标签和元素组成的,每个标签或元素都是很多属性,好比一个人   id 和name 可以看作一个人的身份证号和姓名.下面看下教育局招生系统的用户名输入 ...

  3. Web开发环境搭建 Eclipse-Java EE 篇

    Web开发环境搭建 Eclipse-Java EE 篇 [原创内容,转载注名出处] 1. 下载和安装 1.1 下载JDK 在Java官方网站下载最新版本的 Java SE:  http://www.o ...

  4. define 的全部使用方法

    typedef的总结,以下是引用的内容(红色部分是我自己写的内容). 用途一: 定义一种类型的别名,而不只是简单的宏替换.可以用作同时声明指针型的多个对象.比如: char* pa, pb; // 这 ...

  5. C/C++ 数据结构之算法

    数据结构中的排序算法. 排序算法的相关知识: (1)排序的概念:所谓排序就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来. (2)稳定的排序方法:在待排序的文件中,若存在多个关键字相同的 ...

  6. Struts标签库详解【2】

    ==================================================================== 需要在head中用<s:head />标签 一.控 ...

  7. 怎么确定Oracle客户端安装成功

    可通过能否登录sqlplus来判断是否安装成功. 操作系统:windows10 oracle版本:oracle 11g 步骤: 1.电脑win键+R键,输入cmd,进入命令提示符. 2.命令行中输入: ...

  8. SSM-SpringMVC-19:SpringMVC中请求和响应的乱码解决

     ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 配置一道拦截器即可解决乱码 配置方式如下: 在web.xml中: <!--过滤器处理乱码--> ...

  9. SSM-MyBatis-10:Mybatis中SqlSession的getMapper()和简单的工具类MyBatisUtils

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- getMapper的作用,获取到接口,直接通过点的方式调用方法,以免直接手打的方式写错方法名,(强类型的方式) ...

  10. Linux下启动时间优化专题

    1. 过往优化总结 陆陆续续在Linux进行启动时间优化,之前有两份文档,分别从内核和用户空间两个方向进行了优化. <Android/Linux boot time分析优化>和<Bu ...