DEV Community

Alex
Alex

Posted on

.NET Learning Notes: 配置系统(Configuration)

references:
youtube-yzk
microsoft-configuration

.NET中的配置系统支持丰富的配置源,包括文件(json、xml、ini等)、注册表、环境变量、命令行、Azure Key Vault等,还可以配置自定义配置源。可以跟踪配置的改变,可以按照优先级覆盖。

Json文件配置:
1.创建一个Json文件,文件名随意,比如config.json
2.NuGet安装Microsoft.Extensions.Configuration和Microsoft.Extensions.Configuration.Json
3.编写代码,读取配置. (这里读的是程序运行的目录下的文件,而不是源代码中的文件)

using Microsoft.Extensions.Configuration;

ConfigurationBuilder configurationBuilder= new ConfigurationBuilder();
configurationBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
IConfigurationRoot configRoot = configurationBuilder.Build();
string name = configRoot["name"];
Console.WriteLine(name);
string address = configRoot.GetSection("proxy:address").Value;
Console.WriteLine(address);
Enter fullscreen mode Exit fullscreen mode

绑定读取配置:
1.可以绑定一个类,自动完成配置的读取。
2.NuGet安装:Microsoft.Extensions.Configuration.Binder

using Microsoft.Extensions.Configuration;

ConfigurationBuilder configurationBuilder= new ConfigurationBuilder();
configurationBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
IConfigurationRoot configRoot = configurationBuilder.Build();
//也可以直接从root去get一个根结点的类,前提是这个泛型和json结构一致
Proxy proxy = configRoot.GetSection("proxy").Get<Proxy>(); 
Console.WriteLine($"{proxy.Address}, {proxy.Port}");

class Proxy
{
public string Address {get; set;}
public int Port {get; set;}
}
Enter fullscreen mode Exit fullscreen mode

选项方式读取:
1.推荐使用选项方式读取,和DI结合更好,且更好利用reloadonchange机制
2.NuGet安装:Microsoft.Extensions.OPtions以及上面安装的其他三个拓展包
3.读取配置的时候,DI要声明IOptions、IOptionsMonitor , IOptionsSnapshot等类型. IOptions不会读取到新的值;和IOptionsMonitor相比,IOptionsSnapshot会在同一个范围内(比如一个request中)保持一致。建议使用IOptionsSnapshot。

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

IServiceCollection services = new ServiceCollection();
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("config.json", optional: false, reloadOnChange: true);
IConfigurationRoot configRoot = configurationBuilder.Build();

// options 先添加到DI框架, IOptions{model}就可以被注入了。
// 然后把Config对象绑定到根节点上去,Configure<绑定的类型>(哪个对象绑定)
services.AddOptions().Configure<Config>(e => configRoot.Bind(e))

.Configure<Proxy>(e => configRoot.GetSection("proxy").Bind(e));

services.AddScoped<TestController>();
services.AddScoped<TestController2>();


using (var sp = services.BuildServiceProvider())

{
while (true)

{
using (var scope = sp.CreateScope())

{
// scope改变,snapshot就会改变配置
var c = scope.ServiceProvider.GetRequiredService<TestController>();
c.Test();

// scope不变,snapshot就不会改变配置
var c2 = sp.GetRequiredService<TestController2>();

c2.Test();
}


Console.WriteLine("press any button");
Console.ReadLine();
}
}

class Config

{
public string Name { get; set; }

public int Age { get; set; }

public Proxy Proxy { get; set; }

}
class Proxy

{
public string Address { get; set; }

public int Port { get; set; }

}

using Microsoft.Extensions.Options;

class TestController

{
private readonly IOptionsSnapshot<Config> optConfig;

public TestController(IOptionsSnapshot<Config> optConfig)

{
this.optConfig = optConfig;

}


public void Test()

{
Console.WriteLine(optConfig.Value.Age);
Console.WriteLine("********");
}
}

using Microsoft.Extensions.Options;
class TestController2
{
private readonly IOptionsSnapshot<Proxy> optConfig;

public TestController2(IOptionsSnapshot<Proxy> optConfig)

{
this.optConfig = optConfig;

}

public void Test()

{
Console.WriteLine(optConfig.Value.Address);
Console.WriteLine("********");
}
}
Enter fullscreen mode Exit fullscreen mode

除了JSON文件配置,还有其他配置方式:
命令行方式配置:
1.配置框架还支持从命令行参数、环境变量等地方读取。
2.NuGet安装Microsoft.Extensions.Configuration.CommandLine
3.configBuilder.AddCommandLine(args)
4.参数支持多种格式,server=127.0.0.1、 —server=127.0.0.1、—server 127.0.0.1
CommandLine方式的配置,在程序运行时就确定了,所以没有运行时改配置的问题。
对于{"A" : {"B": "b", "C": "c"}}这种结构,命令行配置没有直接的配置方式(key=value),可以采用扁平化配置。
1.对于环境变量、命令行等简单的键值对结构,如果想要进行复杂结构的配置,需要进行“扁平化处理”。对于配置的名字需要采用层级配置。例如:a🅱️c=jjj, 对于数组:a🅱️c:0=j, a🅱️c:1=k

环境变量配置:
1.NuGet安装:Microsoft.Extensions.Configuration.EnvironmentVariables
2.configurationBuilder.AddEnvironmentVariables(), AddEnvironmentVariables有无参数和有prefix参数的两个重载。无参数版本会把程序相关的所有环境变量都加载进来,由于有可能和系统中已有的环境变量冲突,因此建议用有prefix参数的函数。读取配置时,prefix参数会被忽略,例如 C1_name,读取的时候属性还是name。
3.VS调试的时候,避免修改环境变量,直接在vs中设置。

自定义配置解析:
这里解析了一个web.config的文件,主要的过程是:

  • 首先在configurationBuilder中去添加对应的配置源,这里采用的是FxConfigSource,类似于json、命令行等配置源,在Build函数中,会返回一个对应的provider。
  • FxConfigProvider就是刚刚添加数据源的对应的解析类,解析的过程中,将数据源的格式转换成框架使用的扁平格式,如A:B:C:D,这样系统就可以认识这个配置了(this.Data)。
  • 调用configurationBuilder.Build函数,这个函数就是调用了配置provider的load函数,将数据解析到框架中
  • 通过Options、DI,将WebConfig这个类和解析的数据进行构建和绑定。当需要使用的时候通过DI构造并注入到需要使用的类中,就可以使用这些配置了。
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

ServiceCollection services = new ServiceCollection();
services.AddScoped<TestWebConfig>();
ConfigurationBuilder configurationBuilder= new ConfigurationBuilder();

configurationBuilder.AddUserSecrets<Program>();
configurationBuilder.AddFxConfig("web.config");
// configurationBuilder.AddCommandLine(args);

IConfigurationRoot configurationRoot = configurationBuilder.Build();
services.AddOptions().Configure<WebConfig>(e => configurationRoot.Bind(e));
using (var sp = services.BuildServiceProvider())

{
var c = sp.GetRequiredService<TestWebConfig>();

c.Test();
}

using Microsoft.Extensions.Configuration;
static class FxConfigExtensions

{
public static IConfigurationBuilder AddFxConfig(this IConfigurationBuilder builder, string path = null)

{
return builder.Add(new FxConfigSource() { Path = path });

}
}

using Microsoft.Extensions.Configuration;

class FxConfigSource : FileConfigurationSource

{
public override IConfigurationProvider Build(IConfigurationBuilder builder)

{
EnsureDefaults(builder);
return new FxConfigProvider(this);

}
}
using System.Xml;

using Microsoft.Extensions.Configuration;

class FxConfigProvider : FileConfigurationProvider

{
public FxConfigProvider(FxConfigSource source) : base(source)

{


}


public override void Load(Stream stream)

{
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

XmlDocument xmlDocument = new XmlDocument();

xmlDocument.Load(stream);
var csNodes = xmlDocument.SelectNodes("/configuration/connectionStrings/add");

if (csNodes != null)

{
foreach (XmlNode xmlNode in csNodes.Cast<XmlNode>())

{
string name = xmlNode.Attributes["name"].Value;

string connectionString = xmlNode.Attributes["connectionString"].Value;

data[$"{name}:connectionString"] = connectionString;

var providerName = xmlNode.Attributes["providerName"];

if (providerName != null)

{
data[$"{name}:providerName"] = providerName.Value;

}
}
}
var asNodes = xmlDocument.SelectNodes("/configuration/appSettings/add");

if (asNodes != null)

{
foreach(XmlNode xmlNode in asNodes.Cast<XmlNode>())

{
string key = xmlNode.Attributes["key"].Value;

key = key.Replace(".", ":");

string value = xmlNode.Attributes["value"].Value;

data[key] = value;

}
}
this.Data = data;

}
}

class WebConfig

{
public connectionStr Conn1 { get; set; }

public connectionStr Conn2 { get; set;}

public Config Config{ get; set;}

}


class connectionStr

{
public string connectionString { get; set; }

public string providerName { get; set; }

}


class Config

{
public string Name { get; set; }

public string Age { get; set; }

public Proxy proxy{ get; set; }

}


class Proxy

{
public string Address { get; set; }

public string Port { get; set; }

public string[] ids { get; set; }

}
<configuration>
    <connectionStrings>
        <add name="conn1" connectionString="sdfsdsdf" providerName="abc"/>
        <add name="conn2" connectionString="ewqerqweq" providerName="sql"/>
    </connectionStrings>
    <appSettings>
        <add key="Config:name" value="yzk"/>
        <add key="Config:proxy:address" value="mxncvnxmcn"/>
        <add key="Config:proxy:prot" value="123123"/>
        <add key="Config:proxy:ids:0" value="0"/>
        <add key="Config:proxy:ids:1" value="1"/>
    </appSettings>
</configuration>
Enter fullscreen mode Exit fullscreen mode

配置全部写到源代码中,可以被泄漏:
1.可以将配置写入本地环境变量
2.user-secrets(开发过程中的不应该泄漏的配置,不会加密配置,只适用开发)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay