为什么做这么一个工具

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

您可以在Startup的ConfigureService方法中添加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 接口有SayInChinese 和SayInEnglish 两个实现,我们只想把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 接口有 SayInChinese 和 SayInEnglish两个实现, 我们我们如何获取我们想要的服务实例呢?

 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. Beta 冲刺day3

    1.昨天的困难,今天解决的进度,以及明天要做的事情 昨天的困难:昨天主要是对第三方与企业复杂的逻辑关系进行分析和优化,以及进行部分模块的功能测试和代码测试. 今天解决的进度:根据前天得到的需求问题进行 ...

  2. flex 生成多边形时内、外环计算

    //顺时钟 var pA:Array = [{x:"2969925.6674000006",y:"476254.4874999998"},{x:"29 ...

  3. 有终将被编程潮流淹没的程序员,那是因为没学python人工智能吧?

    2017年被称为中国人工智能元年--在两会时期,人工智能曾经成为国度计策,同时被写进当局报告.在从前的一年里,人工智能获得注重,已逐步浸透到别的行业,无人超市.主动驾驶.人脸识别.智能家居等" ...

  4. python访问mysql

    1,下载mysql-connector-python-2.0.4  pythoin访问mysql需要有客户端,这个就是连接mysql的库 解压后如下图: 双击lib 以windows为例 把mysql ...

  5. JavaScript和JQuery的区别

    一.本质上的区别 1.JavaScript 是通过<script></script>标签插入到HTML页面,可由所有的现代浏览器执行的一种轻量级的编程语言. 2.JQuery是 ...

  6. IT连创业系列:近期功能调整(小魔术功能从二级目录调整到一级栏目)

    最近花了点时间,折腾了一下.NET Core,因此有几篇 Taurus.MVC + CYQ.Data 的文章出来. 这两天也顺带把 ASP.NET Aries 升级了一下功能, 也计划支持.NET C ...

  7. 杨老师课堂之JavaScript定时器_农夫山泉限时秒杀案例

    预览效果图: 使用到的知识点: 定时器 setInterval(函数,毫秒):在指定的毫秒数后调用函数或执行一段代码 取消定时器 clearInterval:取消由setInterval设置的定时器 ...

  8. ajax如何实现、readyState五中状态的含义

    转载:http://www.cnblogs.com/teroy/p/3917439.html 熟悉web开发的程序员想必对Ajax也不会陌生.现在已经有很多js框架封装了ajax实现,例如JQuery ...

  9. PAT1113: Integer Set Partition

    1113. Integer Set Partition (25) 时间限制 150 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue ...

  10. AndroidEclipse里的视图里想添加SDK Manager但是找不到怎么办?

    有时候,我们想配置SDK Manager,但是发现找不到这个窗口,怎么办呢,网上有解释(上截图): —————————————————————————————————————————————————— ...