为什么做这么一个工具

因为我们的系统往往时面向接口编程的,所以在开发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. linux系统下安装tomcat服务器

    一.首先需要关闭linux防火墙(重启后生效) chkconfig iptables off 二.从官网上下载Linux合适版本的tomcat,我现在下来的文件为apache-tomcat-8.5.3 ...

  2. AngularJS 最常用的几种功能

    AngularJS 最常用的几种功能 2017-04-13 吐槽阿福 互联网吐槽大会 第一 迭代输出之ng-repeat标签ng-repeat让table ul ol等标签和js里的数组完美结合 1 ...

  3. 【转】js 好的程序设计,应该什么时候使用 try catch 呢?

    比如在检测浏览器是否支持某些功能的时候 if (!xx) { console.error('此浏览器不支持 xx 功能') } 还是 try { xx; } catch(e) { throw new ...

  4. java客户端调用webService

    啥也不想说,以前使用的方法突然不行了.各种网搜(记得别忘记到jar包哦:axis.jar) 看代码,第一种方式,也就是以前的方式: 改方式不用表名参数名称 public static String i ...

  5. 并发容器之CopyOnWriteArraySet

    CopyOnWriteArraySet是基于CopyOnWriteArrayList实现的,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsent方法. 具体 ...

  6. 初识函数库libpcap

    由于工作上的需要,最近简单学习了抓包函数库libpcap,顺便记下笔记,方便以后查看 一.libpcap简介    libpcap(Packet Capture Library),即数据包捕获函数库, ...

  7. Mybatis通过注解方式实现批量插入数据库 及 常见的坑

    原文地址:http://f0rb.iteye.com/blog/1207384 MyBatis中通过xml文件配置数据库批量操作的文章很多,比如这篇http://www.cnblogs.com/xcc ...

  8. 常见js特效的思路

    1.焦点轮播路 1.布局:父容器用overflow:hidden隐藏多余的图片 2:通过ID获取到重要的元素(父容器.图片列表.左右切换按钮等) 给左右按钮加上点击事件,通过JS更新图片的位置,判断边 ...

  9. ucloud中的udisk错误“Read-only file system”修复指南

    当udisk写入数据提示错误:"Read-only file system",按照下面的方法修复:  1. 停止使用对应udisk的业务 如果有未知的进程正在操作这个硬盘,可使用命 ...

  10. HTML5 CSS3 专题 :诱人的实例 3D展示商品信息

    强化下perspective和transform:translateZ的用法.传统的商品展示或许并不能很好的吸引用户的注意力,但是如果在展示中添加适当的3D元素,~说不定效果不错哈~ 效果图: 说明一 ...