Skip to main content

4 配置系统

读取配置文件

var configBuilder = new ConfigurationBuilder();
configBuilder.AddJsonFile("config.json", optional: true, reloadOnChange: true);
var config = configBuilder.Build();
var name = config["name"];
Console.WriteLine(name);
var add = config.GetSection("proxy:add").Value;
Console.WriteLine(add);

var add2 = config.GetSection("proxy").Get<ProxyConfig>();
Console.WriteLine(add2.Add);



public class ProxyConfig
{
    public string Add { get; set; }
}

选项方式读取配置

读取配置时, 有3种类型

  • IOptions<T>
    • 配置改变后, 不能读取到新的值, 必须重启程序才可以读到新的值
    • 不监听配置的改变, 资源占用较少, 适用于对服务器启动后就不会改变的值进行读取
  • IOptionsMonitor<T>
    • 在配置改变后, 总能读到新的值
  • IOptionsSnapshot<T>
    • 在一个作用范围(如 Scope)内配置的值会保持一致(即使期间值更新了), 当离开作用范围后再次进入范围时, 才会读取到最新的值
    • 使用 IOptionsSnapshot 更符合大部分场景的需求

使用命令行或环境变量时, 对于复杂结构的配置, 需要采用层级配置, 如: Proxy:Add=1000, 表示 Proxy 对象中的 Add 属性值为1000, 如果是数组, 就是这样: Num:0=1, Num:1=2

configBuilder.Addxxx() 可以指定多个, 如果存在同名, 优先使用最后的配置的值(后来的覆盖前边的配置值)

var configBuilder = new ConfigurationBuilder();

// 通过json文件
// configBuilder.AddJsonFile("config.json", optional: true, reloadOnChange: true);

// 通过命令行 
//configBuilder.AddCommandLine(args);

// 通过环境变量(一般加个前缀, 避免和其它环境变量混在一块)
configBuilder.AddEnvironmentVariables();

var configRoot = configBuilder.Build();

ServiceCollection services = new ServiceCollection();
services.AddOptions().Configure<ProxyConfig>((e) => { configRoot.Bind("proxy", e); });
services.AddScoped<TestConfigConroller>();

using (var sp = services.BuildServiceProvider())
{
    while (true)
    {
        // 创建Scope来产生一个范围, IOptionsSnapshot在这个范围内保持数值一致, 离开作用范围之后, 重新获取数值时会取得最新的值
        using (var sc = sp.CreateScope())
        {
            var tc = sc.ServiceProvider.GetRequiredService<TestConfigConroller>();
            tc.Test();
            
            Console.WriteLine("更改配置文件, 可以发现下边读出来的值还是一样的");
            Console.ReadKey();
            
            var tc2 = sc.ServiceProvider.GetRequiredService<TestConfigConroller>();
            tc2.Test();
        }

        Console.WriteLine("出Scope的范围了, 按下以继续");
        Console.ReadKey();
    }
    
}



public class TestConfigConroller
{
    private readonly IOptionsSnapshot<ProxyConfig> _proxyConfig;


    public TestConfigConroller(IOptionsSnapshot<ProxyConfig> proxyConfig)
    {
        _proxyConfig = proxyConfig;
    }

    public void Test()
    {
        Console.WriteLine(_proxyConfig.Value.Add);
        Console.WriteLine("----------");
        Console.WriteLine(_proxyConfig.Value.Add);
    }
    
}

config.json

{
  "name": "abc",
  "proxy": {
    "add": "192.168"
  }
}

自定义配置提供者(读取XML)

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <connectionStrings>
        <add name="conn" connectionString="Data Source=.;Initial Catalog=DB;User ID=sa;Password=1234" providerName="Hsin.SqlClient"/>
        <add name="conn2" connectionString="Data Source=127.0.0.1;Initial Catalog=DB;User ID=sa;Password=12345" providerName="Hsin.MyClient"/>
    </connectionStrings>
    <appsettings>
        <add key="config:name" value="smtp.test.com"/>
        <add key="config:proxy:add" value="abc123"/>
        <add key="config:proxy:ids:0" value="12"/>
        <add key="config:proxy:ids:1" value="45"/>
    </appsettings>
</configuration>

var configBuilder = new ConfigurationBuilder();

configBuilder.AddFxConfig("web.config");

var configRoot = configBuilder.Build();

var services = new ServiceCollection();
services.AddOptions().Configure<WebConfig>(e => configRoot.Bind(e));
services.AddScoped<TestWebConfig>();

using (var serviceProvider = services.BuildServiceProvider())
{
    var tw = serviceProvider.GetRequiredService<TestWebConfig>();
    tw.Test();
}




public class WebConfig
{
    public ConnectionStr Conn { get; set; }

    public ConnectionStr Conn2 { get; set; }

    public Config Config { get; set; }
}

public class Config
{
    public string Name { get; set; }

    public ProxyConfig Proxy { get; set; }
}

public class ProxyConfig
{
    public string Add { get; set; }

    public List<int> Ids { get; set; }
}

public class ConnectionStr
{
    public string ConnectionString { get; set; }

    public string ProviderName { get; set; }
}

public class FxConfigProvider : FileConfigurationProvider
{
    public FxConfigProvider(FileConfigurationSource source) : base(source)
    {
    }

    public override void Load(Stream stream)
    {
        var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        var xd = new XmlDocument();
        xd.Load(stream);

        var csNodes = xd.SelectNodes("/configuration/connectionStrings/add");
        foreach (var node in csNodes.Cast<XmlNode>())
        {
            var name = node.Attributes["name"].Value;
            var connectionString = node.Attributes["connectionString"].Value;

            // 多层级的属性
            data[$"{name}:connectionString"] = connectionString;

            var attProviderName = node.Attributes["providerName"];
            if (attProviderName != null)
            {
                data[$"{name}:providerName"] = attProviderName.Value;
            }
        }

        var asNodes = xd.SelectNodes("/configuration/appsettings/add");
        foreach (var node in asNodes.Cast<XmlNode>())
        {
            var key = node.Attributes["key"].Value;
            key = key.Replace(".", ":");
            var value = node.Attributes["value"].Value;

            data[key] = value;
        }
        
        Data = data;
    }
}

public class FxConfigSource : FileConfigurationSource
{
    public override IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        // 加这一句能够正确获取到目录下的文件(不然会提示找不到文件)
        EnsureDefaults(builder);
        return new FxConfigProvider(this);
    }
}

public static class FxConfigExtensions
{
    // 写Addxxx来简化过程
    public static IConfigurationBuilder AddFxConfig(this IConfigurationBuilder builder, string path = null)
    {
        if (path == null)
        {
            path = "web.config";
        }
        builder.Add(new FxConfigSource { Path = path });
        return builder;
    }
}

public class TestWebConfig
{
    private readonly IOptionsSnapshot<WebConfig> _webConfig;

    public TestWebConfig(IOptionsSnapshot<WebConfig> webConfig)
    {
        _webConfig = webConfig;
    }

    public void Test()
    {
        var wc = _webConfig.Value;
        Console.WriteLine($"Conn:ConnectionString: {wc.Conn.ConnectionString}, ProviderName: {wc.Conn.ProviderName}");
        Console.WriteLine($"Config:Name: {wc.Config.Name}, Config:Proxy:Add: {wc.Config.Proxy.Add}");
    }
}