迈向现代化的.NET配置指北
一、欢呼 .NET Standard 时代
我现在已不大提 .NET Core,对于我来说,未来的开发将是基于 .NET Standard,不仅仅是面向未来 ,也是面向过去;不只是 .NET Core 可以享受便利, .NET Framework 不升级一样能享受 .NET Standard 带来的好处。
(目前 .NET Standard 支持 .NET Framework 4.6.1+)
二、传统配置的不足
在我刚步足 .NET 的世界时,曾经有过一个 困惑,是不是所有的配置都必须写在 Web.Config 中?而直到开始学习 .Net Core 的配置模式,才意识到传统配置的不足:
除了 XML ,我们可能还需要更多的配置来源支持,比如 Json
配置是否可以直接序列化成对象或者多种类型(直接取出来就是 int),而不只是 string
修改配置后,IIS 就重启了,是否有办法不重启就能修改配置
微服务(或者说分布式)应用下管理配置带来的困难
很显然微软也意识到这些问题,并且设计出了一个强大并且客制化的配置方式,但是这也意味着从 AppSettings 中取出配置的时代也一去不复返。
三、初识 IConfiguration
在开始探讨现代化配置设计之前,我们先快速上手 .Net Core 中自带的 Microsoft.Extensions.Configuration。
如前面提到的,这不是 .Net Core 的专属。
我们首先创建一个基于 .NET Framework 4.6.1 的控制台应用代码地址:(https://github.com/jonechenug/ZHS.Configuration.Sample/blob/master/src/ZHS.Configuration.NFX.Sample/Program.cs),然后安装我们所需要的依赖。
Nuget Install Microsoft.Extensions.Configuration.Json
Nuget Install Microsoft.Extensions.Configuration.Binder
然后引入我们的配置文件 my.conf:
{
"TestConfig": {
"starship": {
"name": "USS Enterprise",
"registry": "NCC-1701",
"class": "Constitution",
"length": 304.8,
"commissioned": false
},
"trademark": "Paramount Pictures Corp. http://www.paramount.com"
}
}
最后,输入如下的代码,并启动:
var configurationBuilder = new ConfigurationBuilder().AddJsonFile("my.conf", optional: true, reloadOnChange: true)
.AddInMemoryCollection(new List<KeyValuePair<String, String>>
{
new KeyValuePair<String,String>("myString","myString"),
new KeyValuePair<String,String>("otherString","otherString")
});
IConfiguration config = configurationBuilder.Build();
String myString = config["myString"]; //myString
TestConfig testConfig = config.GetSection("TestConfig").Get<TestConfig>();
var length = testConfig.Starship.Length;//304.8
Console.WriteLine($"myString:{myString}");
Console.WriteLine($"myString:{JsonConvert.SerializeObject(testConfig)}");
Console.ReadKey();
微软 支持 的来源除了有内存来源、还有系统变量、Json 文件、XML 文件等多种配置来源,同时社区的开源带来了更多可能性,还支持诸如 consul、etcd 和 apollo 等 分布式配置中心。
除了支持更多的配置来源外,我们还观察到,来源是否可以 缺省 、是否可以 重载 ,都是可以配置的。特别是自动重载,这在 .NETFramework 时代是无法想象的,每当我们修改 Web.config的配置文件时,热心的 IIS 就会自动帮我们重启应用,而用户在看到 500 的提示或者一片空白时,不禁会发出这网站真烂的赞美。(同时需要注意配置 iis 的安全,避免可以直接访问配置的 json 文件,最好的方法是把json后缀改为诸如 conf 等)
四、配置防腐层
虽然微软自带的 IConfiguration 已经足够用了,但是让我们畅享下未来,或者回到我让我困惑的问题。是不是所有的配置都将基于 IConfiguration ?
答案自然是否定的,编程技术不停地在发展,即使老而弥坚的 AppSetting 也难逃被淘汰的一天。所以为了让我们的架构更长远一些,我们需要进行 防腐层的设计。
而且,如果你还在维护以前的老项目时,你更是需要借助防腐层的魔法去抵消同事或者上司的顾虑。
让我们重新审视配置的用法,无非就是从某个 key 获取对应的值(可能是字符串、也可能是个对象),所以我们可以在最底层的类库或全局类库中定义一个 IConfigurationGeter 来满足我们的要求。
namespace ZHS.Configuration.Core
public interface IConfigurationGeter
{
TConfig Get<TConfig>(string key);
String this[string key] { get;}
}
而关于 IConfigurationGeter的实现,我们姑且叫它 ConfigurationGetter ,基于防腐层的设计,我们不能在底层的类库安装任何依赖。所以我们需要新建一个基础设施层或者在应用入口层实现。(代码示例中可以看到是在不同的项目中)
namespace ZHS.Configuration.DotNetCore
public class ConfigurationGetter : IConfigurationGeter
{
private readonly IConfiguration _configuration;
public ConfigurationGetter(IConfiguration configuration)
{
_configuration = configuration;
}
public TConfig Get<TConfig>(string key)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("Value cannot be null or whitespace.", nameof(key));
var section = _configuration.GetSection(key);
return section.Get<TConfig>();
}
public string this[string key] => _configuration[key];
}
以后我们所有的配置都是通过 IConfigurationGeter 获取,这样就避免了在你的应用层(或者三层架构中的 BAL 层) 中引入 Microsoft.Extensions.Configuration 的依赖。
当然可能有些人会觉得大材小用,但实际上等你到了真正的开发,你就会觉得其中的好处。不止是我.NET Core 的设计者早就意识到防腐层的重要性,所以才会有 Microsoft.Extensions.Configuration.Abstractions 等一系列的只有接口的抽象基库。
五、静态获取配置
虽然我们已经有了防腐层,但显然我们还没考虑到实际的用法,特别是如果你的应用还没有引入依赖注入的支持,我们前面实现的防腐层对于你来说,就是摸不着头脑。
同时,我还是很喜欢以前那种直接从 AppSetting 中取出配置的便捷。
所以,这里我们需要引入 服务定位器模式 来满足 静态获取配置 的便捷操作。
namespace ZHS.Configuration.Core
public class ConfigurationGeterLocator
{
private readonly IConfigurationGeter _currentServiceProvider;
private static IConfigurationGeter _serviceProvider;
public ConfigurationGeterLocator(IConfigurationGeter currentServiceProvider)
{
_currentServiceProvider = currentServiceProvider;
}
public static ConfigurationGeterLocator Current => new ConfigurationGeterLocator(_serviceProvider);
public static void SetLocatorProvider(IConfigurationGeter serviceProvider)
{
_serviceProvider = serviceProvider;
}
public TConfig Get<TConfig>(String key)
{
return _currentServiceProvider.Get<TConfig>(key);
}
public String this[string key] => _currentServiceProvider[key];
}
public static IConfiguration AddConfigurationGeterLocator(this IConfiguration configuration)
{
ConfigurationGeterLocator.SetLocatorProvider(new ConfigurationGetter(configuration));
return configuration;
}
做完这些基础工作,我们还需要在应用入口函数念一句咒语让他生效。
config.AddConfigurationGeterLocator();
var myString = ConfigurationGeterLocator.Current["myString"];
// "myString"
现在,我们就能像以前一样,直接调用 ConfigurationGeterLocator.Current 来获取我们想要的配置了。
六、依赖注入的曙光
现在假设我们摆脱了蛮荒时代,有了依赖注入的武器,使用配置最方便的用法莫不过直接注入一个配置对象,在 .NET Core 中做法大致如下:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<TestConfig>(provider =>Configuration.GetSection("TestConfig").Get<TestConfig>());
}
而它的使用就十分方便:
public class ValuesController : ControllerBase
{
private readonly TestConfig _testConfig;
public ValuesController(TestConfig testConfig)
{
_testConfig = testConfig;
}
// GET api/values
[HttpGet]
public JsonResult Get()
{
var data = new
{
TestConfig = _testConfig
};
return new JsonResult(data);
}
}
看到这里你可能会困惑,怎么和官方推荐的 IOptions
(https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.1 ) 用法不一样? 尽管它在官方文档备受到推崇,然而在实际开发中,我是几乎不会使用到的,在我看来:
不使用 IOptions就已经得到了对应的效果
使用 IOptionsSnapshot 才能约束配置是否需要热重载,但实际这个并不好控制(所以鸡肋)
我们已经有防腐层了,再引入就是破坏了设计
七、约定优于配置的福音
在微服务应用流行的今天,我们需要的配置类会越来越多。我们不停地注入,最终累死编辑器,是否有自动化注入的方法来解放我们的键盘?答案自然是有的,然而在动手实现之前,我们需要立下 约定优于配置 的海誓山盟。
首先,对于所有的配置类,他们都可以看作是一类或者某个接口的实现。
public interface IConfigModel{ }
public class TestConfig : IConfigModel
{
public String DefauleVaule { get; set; } = "Hello World";
public Starship Starship { get; set; }
public string Trademark { get; set; }
}
public class Starship
{
public string Name { get; set; }
public string Registry { get; set; }
public string Class { get; set; }
public float Length { get; set; }
public bool Commissioned { get; set; }
}
联想我们刚刚注入 TestConfig 的时候,是不是指定了配置节点 "TestConfig" ,那么如果我们要自动注入的话,是不是可以考虑直接使用类的唯一标志,比如类的全名,那么注入的方法就可以修改为如下:
public static IServiceCollection AddConfigModel(this IServiceCollection services)
{
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(IConfigModel))))
.ToArray();
foreach (var type in types)
{
services.AddScoped(type, provider =>
{
var config = provider.GetService<IConfiguration>().GetSection(type.FullName).Get(type);
return config;
});
}
return services;
}
仅仅用了类的全名还不够体现 约定优于配置 的威力,联系现实,是不是配置的某些选项是有默认值的,比如 TestConfig 的 DefauleVaule 。
在没有配置 DefauleVaule 的情况下,DefauleVaule 的值将为 默认值 ,即我们代码中的 "Hello World" ,反之设置了 DefauleVaule 则会覆盖掉原来的默认值。
八、分布式配置中心
在微服务流行的今天,如果还是像以前一样人工改动配置文件,那是十分麻烦而且容易出错的一件事情,这就需要引入配置中心,同时配置中心也必须是分布式的,才能避免单点故障。
8.1、Consul
Consul 目前是我的首选方案,首先它足够简单,部署方便,同时已经够用了。如果你还使用过 Consul,可以使用 Docker 一键部署:
docker run -d -p 8500:8500 --name consul consul
然后在应用入口项目中引入 Winton.Extensions.Configuration.Consul的依赖。
因为是个人开源,所以难免会有一些问题,比如我装的版本就是 2.1.0-master0003,它解决了 2.0.1 中的一些问题,但还没有发布正式版。
8.1.1 、.NETCore 使用 Consul 配置中心
如果你是 .Net Core 应用,你需要在 Program.cs 配置 ConfigureAppConfiguration:
public class Program
{
public static readonly CancellationTokenSource ConfigCancellationTokenSource = new CancellationTokenSource();
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((builderContext, config) =>
{
IHostingEnvironment env = builderContext.HostingEnvironment;
var tempConfigBuilder = config;
var key = $"{env.ApplicationName}.{env.EnvironmentName}";//ZHS.Configuration.DotNetCore.Consul.Development
config.AddConsul(key, ConfigCancellationTokenSource.Token, options =>
{
options.ConsulConfigurationOptions =
co => { co.Address = new Uri("http://127.0.0.1:8500"); };
options.ReloadOnChange = true;
options.Optional = true;
options.OnLoadException = exceptionContext =>
{
exceptionContext.Ignore = true;
};
});
})
.UseStartup<Startup>();
}
同时由于 .Net 客户端与 Consul 之间交互会使用长轮询,所以我们需要在关闭应用的同时也要记得把连接回收,这就需要在 Startup.cs 的 Configure 中处理:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime appLifetime)
{
appLifetime.ApplicationStopping.Register(Program.ConfigCancellationTokenSource.Cancel);
}
8.1.2、.NET Framework 使用 Consul 配置中心
同理,对于 .NET Framework 应用来说,也是需要做对应的处理,在 Global.asax 中:
public class WebApiApplication : System.Web.HttpApplication
{
public static readonly CancellationTokenSource ConfigCancellationTokenSource = new CancellationTokenSource();
protected void Application_Start()
{
AddConsul();
GlobalConfiguration.Configure(WebApiConfig.Register);
}
private static void AddConsul()
{
var config = new ConfigurationBuilder();
config.AddConsul("ZHS.Configuration.DotNetCore.Consul.Development", ConfigCancellationTokenSource.Token, options =>
{
options.ConsulConfigurationOptions =
co => { co.Address = new Uri("http://127.0.0.1:8500"); };
options.ReloadOnChange = true;
options.Optional = true;
options.OnLoadException = exceptionContext =>
{
exceptionContext.Ignore = true;
};
});
//var test = config.Build();
config.Build().AddConfigurationGeterLocator();
}
protected void Application_End(object sender, EventArgs e)
{
ConfigCancellationTokenSource.Cancel();
}
}
8.1.3、配置 Consul
我们所说的配置,对于 Consul 来说,就是 Key/Value 。我们有两种配置,一种是把以前的json配置文件都写到一个key 中。
另一种就是创建一个 key 的目录,然后每个 Section 分开配置。
九、结语
写这篇文章很大的动力是看到不少 .NET Core 初学者抱怨使用配置中的各种坑,抱怨微软文档不够清晰,同时也算是我两年来的一些开发经验总结。
最后,需要谈一下感想。感受最多的莫过于 .NET Core 开源带来的冲击,有很多开发者兴致勃勃地想要把传统的项目重构成 .NET Core 项目,然而思想却没有升级上去,反而越觉得 .NET Core 各种不适。
但是只要思想升级了,即使开发 .NET Framework 应用, 一样也是能享受 .NET Standard 带来的便利。
迈向现代化的.NET配置指北的更多相关文章
- 【长期更新】迈向现代化的 .Net 配置指北
1. 欢呼 .NET Standard 时代 我现在已不大提 .Net Core,对于我来说,未来的开发将是基于 .NET Standard,不仅仅是 面向未来 ,也是 面向过去:不只是 .Net C ...
- Kinect_V1在Debian testing的配置指北
在Linux下驱动Kinect V1现在有两种方式,一种是使用OpenNI + SensorKinect + Nite的方案,一种是使用OpenNI2 + libfreenect的方案,第一种我没有尝 ...
- [Android Studio] 2019年Android Studio配置指北
Android Studio是我学习Android开发路上的第一块绊脚石,新建一个项目,一行代码没动,直接编译不起来,我太难了,所以本文叫指北 本文讲解在9102年如何在国内网络不通畅的情况下流畅的使 ...
- Hive安装配置指北(含Hive Metastore详解)
个人主页: http://www.linbingdong.com 本文介绍Hive安装配置的整个过程,包括MySQL.Hive及Metastore的安装配置,并分析了Metastore三种配置方式的区 ...
- Django快速开发实践:Drf框架和xadmin配置指北
步骤 既然是快速开发,那废话不多说,直接说步骤: 安装Djagno 安装Django Rest Framework 定义models 定义Rest framework的serializers 定义Re ...
- vscode 插件配置指北
Extension Manifest 就像 chrome 插件使用 manifest.json 来管理插件的配置一样,vscode 的插件也有一个 manifest,而且就叫 package.json ...
- git宝典—应付日常工作使用足够的指北手册
最近公司gitlab又迁移,一堆git的命令骚操作,然鹅git命令,感觉还是得复习下——其实,git现在界面操作工具蛮多,比如intellij 自带的git操作插件就不错,gitlab github ...
- 可能比文档还详细--VueRouter完全指北
可能比文档还详细--VueRouter完全指北 前言 关于标题,应该算不上是标题党,因为内容真的很多很长很全面.主要是在官网的基础上又详细总结,举例了很多东西.确保所有新人都能理解!所以实际上很多东西 ...
- 关于supervisor的入门指北
关于supervisor的入门指北 在目前这个时间点(2017/07/25),supervisor还是仅支持python2,所以我们要用版本管理pyenv来隔离环境. pyenv 根据官方文档的讲解, ...
- Celery入门指北
Celery入门指北 其实本文就是我看完Celery的官方文档指南的读书笔记.然后由于我的懒,只看完了那些入门指南,原文地址:First Steps with Celery,Next Steps,Us ...
随机推荐
- 从零开始配置 vim(18)——终端模式
在进入下一个配置之前,先了解一下如何在 neovim 中使用它内置的终端. 我们之前说过在命令模式中可以使用 !来执行shell命令.但是终归来说,执行和使用上不是那么方便,特别是混合使用 vim 命 ...
- 基于 hugging face 预训练模型的实体识别智能标注方案:生成doccano要求json格式
强烈推荐:数据标注平台doccano----简介.安装.使用.踩坑记录_汀.的博客-CSDN博客_doccano huggingface官网 参考:数据标注平台doccano----简介.安装.使用. ...
- HTTP请求头引发的注入问题 (SQL注入)
关于请求头中注入问题的演示,这里我写了一些测试案例,用来测试请求头中存在的问题.我们常见的会发生注入的点有 Referer.X-Forwarded-For.Cookie.X-Real-IP.Accep ...
- LyScriptTools 模块类API接口手册
LyScriptTools工具包是在LyScript模块基础上封装的工具包,其主要是二次封装LyScript插件实现的一些新功能,或者将特定功能组件拆分开形成的独立模块,此类模块可实现更加精细化的功能 ...
- Hadoop超详细讲解之单节点搭建
1 Hadoop介绍 Hadoop是Apache旗下的一个用java语言实现开源软件框架,是一个开发和运行处理大规模数据的软件平台.允许使用简单的编程模型在大量计算机集群上对大型数据集进行分布式处理. ...
- 疾速7600MT/s!KELVV科赋CRAS V RGB DDR5内存图赏
12月18日消息,KLEVV科赋日前推出新款大容量DDR5内存套装,满足游戏玩家.内容创作者和高端PC爱好者的需求. 现在,KLEVV科赋CRAS V RGB内存套装已经来到我们评测室,下面为大家带来 ...
- 使用Visual studio code 进行.NET 开发
Visual studio code 作为一款强大的编辑器,相信很多开发者都用过.vs code 的强大源自开源生态丰富,编辑器本身简单,但是加上各式的插件,就变得无比牛逼,基本可以替代现有的大部分工 ...
- java代码实现自动生成数据库表er图
最近有同事看到字节跳动产品设计文档里有数据库表er图.就想问问又没有现成的工具也给直接生成一个er图,经查找验证发现并没有.因为现在表关系都是用的逻辑外键而非物理外键约束的,所以像navicat等工具 ...
- idea报错 Error running GctlBrpApplication. Command line is too long. Shorten the command line and rerun.解决方案
idea新导入项目有时候会出现以下报错,解决方法如下: 一:报错截图,报错原因是命令行太长,让缩短命令长度再运行. 二:解决方法如下:在剪头标记位置加入代码: <property name=&q ...
- npm script 详解,tsc && electron . 直接编译后 运行,非常方便
最终平时只需要用这个方法就可以了: tsc && electron . ======================================================== ...