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(开发过程中的不应该泄漏的配置,不会加密配置,只适用开发)

Top comments (0)