DEV Community

Alex
Alex

Posted on • Edited on

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

references:
youtube-yzk
microsoft-configuration

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

The initialized WebApplicationBuilder(builder) providers default configuration for the app in the following order, from highest to lowest priority:
1.Commend-line arguments using the Command-line configuration provider
2.Non-prefixed environment variables using the Non-prefixed environment variables configuration provider.
3.User secrets when the app runs in the Development enviroment.
4.appsettings.{Environment}.json using the JSON configuration provider. For example, appsettings.Production.json and appsettings.Develpment.json.
5.appsettings.json using the JSON configuration provider
6.A fallback to the host configuration described in the next section.

While some configuration can be done in both the host and the application configuration providers, generally, only configuration that is necessary for the host should be done in host configuration.
When a configuration value is set in host and application configuration, the application configuration is used.

Read appsettings.json and appsettings.{env}.json
1.Install NeGet package

Microsoft.Extensions.Configuration
Enter fullscreen mode Exit fullscreen mode

Because the default JsonConfigurationProvider loads configuration in the following order: appsettings / appsettings.{Environment}.json, you do not need to register it in the Program.cs
2.Read configuration in these files

// appsettings.json
{
  "Position": {
    "Title": "Editor1",
    "Name": "Joe Smith"
  },
  "MyKey": "My appsettings.json Value",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

public class ConfigurationController(IConfiguration configuration) : ControllerBase
{
  [HttpGet]
    public IActionResult GetConfigurationFromAppSettings()
    {
        // read configuration
        var myKeyValue = configuration["MyKey"];
        // read hierarchical configuration
        var title = configuration["Position:Title"];
        var name = configuration["Position:Name"];
        // if configuration is not exist, return default value.
        var notExistValue = configuration.GetValue("NotExistValue", "default");
}
Enter fullscreen mode Exit fullscreen mode

Bind hierarchical configuration
The preferred way to read related configuration values is using the options pattern. If we want to read Position in a more convenient way.

public class PositionOptions
{
    public const string Position = "Position";

    public string Title { get; set; } = String.Empty;
    public string Name { get; set; } = String.Empty;
}

// Program.cs
builder.Configuration.GetSection(PositionOptions.Position).Bind(new PositionOptions());

// invoke
public class ConfigurationController(IConfiguration configuration) : ControllerBase
{
[HttpGet]
public IActionResult GetConfigurationFromBind()
{
    var position = configuration.GetSection(PositionOptions.Position).Get<PositionOptions>();
    return Ok(position);
}
}
Enter fullscreen mode Exit fullscreen mode

An alternative approach when using the options pattern is to bind the Position section and add it to the dependency injection service container.

// Program.cs
builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
// or
builder.Services.AddOptions().Configure<PositionOptions>(e => builder.Configuration.GetSection(PositionOptions.Position).Bind(e));


// invoke
public class ConfigurationController(IOptions<PositionOptions> options) : ControllerBase
{
    [HttpGet]
    public IActionResult GetConfigurationFromOptions()
    {
        return Ok(options.Value.Name + " : " + options.Value.Title);
    }
}
Enter fullscreen mode Exit fullscreen mode

In Options Pattern, changes to the JSON configuration file after the app has started are not read. To read changes after the app has started, use IOptionsSnapshot(IOptions to IOptionsSnapshot).
The difference between IOptionsMonitor and IOptionsSnapshot is that IOptionsMonitor is a Singleton service, and IOptionsSnapshot is a Scoped service and provides a snapshot of the options at the time the IOptionsSnapshot object is constructed.

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

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

除了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

Top comments (0)