netcore 配置文件中的配置读取

3/11/2022 6:48:21 PM
669
0

首先要知道

IConfigurationRoot.Reload 方法用于重新加载应用程序的配置信息,从而使应用程序能够动态更新配置而无需重启。当应用程序需要在运行时更新配置时,可以使用该方法。

具体来说,IConfigurationRoot.Reload 方法会重新加载配置源(例如 JSON 文件、环境变量、命令行参数等),并更新 IConfigurationRoot 对象中的配置信息。这样,应用程序就可以立即使用新的配置信息而无需重启。

以下是 IConfigurationRoot.Reload 方法的简单示例:

// 创建 ConfigurationBuilder 对象并加载配置
var builder = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

IConfigurationRoot configuration = builder.Build();

// 在应用程序运行时更改了配置文件,然后调用 Reload 方法重新加载配置
// 这里仅作示例,实际场景中可能根据需要更新不同类型的配置源
configuration.Reload();

在上述示例中,当应用程序需要更新配置时,可以调用 IConfigurationRoot.Reload 方法来重新加载配置。需要注意的是,在调用 Reload 方法之后,应用程序会立即开始使用新的配置信息,因此开发人员需要确保在重新加载配置后正确处理新的配置数据。

 

使用选项模式绑定分层配置数据

读取相关配置值的首选方法是使用选项模式。 例如,若要读取以下配置值,请执行以下操作:

JSON复制

 

  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

创建以下 PositionOptions 类:

C#复制

 

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

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

选项类:

  • 必须是包含公共无参数构造函数的非抽象类。
  • 类型的所有公共读写属性都已绑定。
  • 字段不是绑定的。 在上面的代码中,Position 未绑定。 由于使用了 Position 属性,因此在将类绑定到配置提供程序时,不需要在应用中对字符串 "Position" 进行硬编码。

下面的代码:

C#复制

 

public class Test22Model : PageModel
{
    private readonly IConfiguration Configuration;

    public Test22Model(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public ContentResult OnGet()
    {
        var positionOptions = new PositionOptions();
        Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }
}

在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。

ConfigurationBinder.Get<T> 绑定并返回指定的类型。 使用 ConfigurationBinder.Get<T> 可能比使用 ConfigurationBinder.Bind 更方便。 下面的代码演示如何将 ConfigurationBinder.Get<T>PositionOptions 类配合使用:

  public ContentResult OnGet()
    {            
        positionOptions = Configuration.GetSection(PositionOptions.Position)
                                                     .Get<PositionOptions>();

        return Content($"Title: {positionOptions.Title} \n" +
                       $"Name: {positionOptions.Name}");
    }

在上面的代码中,默认读取在应用启动后对 JSON 配置文件所做的更改。

使用选项模式时的另一种方法是绑定 部分,并将其添加到依赖关系注入服务容器中。 在以下代码中,PositionOptions 已通过 Configure 被添加到了服务容器并已绑定到了配置:

C#复制

 

using ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));

var app = builder.Build();
 private readonly PositionOptions _options;

    public Test2Model(IOptions<PositionOptions> options)
    {
        _options = options.Value;
    }

在上面的代码中,不会读取在应用启动后对 JSON 配置文件所做的更改。 若要读取在应用启动后的更改,请使用 IOptionsSnapshot

使用默认配置时,会通过 reloadOnChange: true 启用 和 appsettings..json 文件。 应用启动后,对 appsettings.json 和 appsettings.Environment.json 文件所作的更改将由 JSON 配置提供程序读取。

有关添加其他 JSON 配置文件的信息,请参阅本文档中的 JSON 配置提供程序

  public ContentResult OnGet()
    {
        var myKeyValue = Configuration["MyKey"];
        var title = Configuration["Position:Title"];
        var name = Configuration["Position:Name"];
        var defaultLogLevel = Configuration["Logging:LogLevel:Default"];


        return Content($"MyKey value: {myKeyValue} \n" +
                       $"Title: {title} \n" +
                       $"Name: {name} \n" +
                       $"Default Log Level: {defaultLogLevel}");
    }

读取分层配置数据的首选方法是使用选项模式。 有关详细信息,请参阅本文档中的绑定分层配置数据

GetSectionGetChildren 方法可用于隔离各个节和配置数据中某节的子节。 稍后将在 GetSection、GetChildren 和 Exists 中介绍这些方法。

 

配置键:

  • 不区分大小写。 例如,ConnectionString 和 connectionstring 被视为等效键。
  • 如果在多个配置提供程序中设置了某一键和值,则会使用最后添加的提供程序中的值。 有关详细信息,请参阅默认配置
  • 分层键
    • 在配置 API 中,冒号分隔符 (:) 适用于所有平台。
    • 在环境变量中,冒号分隔符可能无法适用于所有平台。 所有平台均支持采用双下划线 __,并且它会自动转换为冒号 :。
    • 在 Azure Key Vault 中,分层键使用 -- 作为分隔符。 当机密加载到应用的配置中时,Azure Key Vault 配置提供程序 会自动将 替换为 :。
  • ConfigurationBinder 支持使用配置键中的数组索引将数组绑定到对象。 数组绑定将在将数组绑定到类部分中进行介绍。

配置值:

  • 为字符串。
  • NULL 值不能存储在配置中或绑定到对象。
     

GetValue

ConfigurationBinder.GetValue<T> 从配置中提取一个具有指定键的值,并将它转换为指定的类型:

  public ContentResult OnGet()
    {
        var number = Configuration.GetValue<int>("NumberKey", 99);
        return Content($"{number}");
    }

 

GetSection、GetChildren 和 Exists

对于下面的示例,请考虑以下 MySubsection.json 文件:

JSON复制

 

{
  "section0": {
    "key0": "value00",
    "key1": "value01"
  },
  "section1": {
    "key0": "value10",
    "key1": "value11"
  },
  "section2": {
    "subsection0": {
      "key0": "value200",
      "key1": "value201"
    },
    "subsection1": {
      "key0": "value210",
      "key1": "value211"
    }
  }
}

 

GetSection

IConfiguration.GetSection 会返回具有指定子节键的配置子节。

以下代码将返回 section1 的值:

  private readonly IConfiguration Config;

    public TestSectionModel(IConfiguration configuration)
    {
        Config = configuration.GetSection("section1");
    }

以下代码将返回 section2:subsection0 的值:

 private readonly IConfiguration Config;

    public TestSection2Model(IConfiguration configuration)
    {
        Config = configuration.GetSection("section2:subsection0");
    }
    
     public ContentResult OnGet()
    {
        return Content(
                $"section2:subsection0:key0 '{Config["key0"]}'\n" +
                $"section2:subsection0:key1:'{Config["key1"]}'");
    }

GetSection 永远不会返回 null。 如果找不到匹配的节,则返回空 IConfigurationSection。

当 GetSection 返回匹配的部分时,Value 未填充。 存在该部分时,返回一个 KeyPath 部分。

 

GetChildren 和 Exists

以下代码将调用 IConfiguration.GetChildren 并返回 的值:

 private readonly IConfiguration Config;

    public TestSection4Model(IConfiguration configuration)
    {
        Config = configuration;
    }

    public ContentResult OnGet()
    {
        string s = "";
        var selection = Config.GetSection("section2");
        if (!selection.Exists())
        {
            throw new Exception("section2 does not exist.");
        }
        var children = selection.GetChildren();

        foreach (var subSection in children)
        {
            int i = 0;
            var key1 = subSection.Key + ":key" + i++.ToString();
            var key2 = subSection.Key + ":key" + i.ToString();
            s += key1 + " value: " + selection[key1] + "\n";
            s += key2 + " value: " + selection[key2] + "\n";
        }
        return Content(s);
    }

前面的代码将调用 ConfigurationExtensions.Exists 以验证该节是否存在:

绑定数组

ConfigurationBinder.Bind 支持使用配置键中的数组索引将数组绑定到对象。 公开数值键段的任何数组格式都能够与 POCO 类数组进行数组绑定。

请考虑示例下载中的 MyArray.json:

JSON复制

 

{
  "array": {
    "entries": {
      "0": "value00",
      "1": "value10",
      "2": "value20",
      "4": "value40",
      "5": "value50"
    }
  }
}

以下代码将 MyArray.json 添加到配置提供程序:


    ArrayExample?    _array = Config.GetSection("array").Get<ArrayExample>();
      _array = Config.GetSection("array").Get<ArrayExample>();
        string s = String.Empty;

        for (int j = 0; j < _array.Entries.Length; j++)
        {
            s += $"Index: {j}  Value:  {_array.Entries[j]} \n";
        }
public class ArrayExample
{
    public string[] Entries { get; set; } = new string[] { "", "" };
}

 

 

绑定分层配置

"Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  }

创建以下 PositionOptions 类:

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

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

选项类:

  • 必须是包含公共无参数构造函数的非抽象类。
  • 类型的所有公共读写属性都已绑定。
  • 字段不是绑定的。 在上面的代码中,Position 未绑定。 由于使用了 Position 字段,因此在将类绑定到配置提供程序时,不需要在应用中对字符串 "Position" 进行硬编码。

调用 ConfigurationBinder.Bind 将 PositionOptions 类绑定到 Position 部分。

var positionOptions = new PositionOptions();
Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);

使用 ConfigurationBinder.Get<T> 可能比使用 ConfigurationBinder.Bind 更方便。 下面的代码演示如何将 ConfigurationBinder.Get<T>PositionOptions 类配合使

positionOptions = Configuration.GetSection(PositionOptions.Position).Get<PositionOptions>();

使用选项模式时的另一种方法是绑定 Position 部分,并将其添加到依赖关系注入服务容器中。 在以下代码中,PositionOptions 已通过 Configure 被添加到了服务容器并已绑定到了配置:

Services.Configure<PositionOptions>(Configuration.GetSection("PositionOptions:Position"));

选项接口

  1. IConfigureOptions<TOptions>

    • IConfigureOptions<TOptions> 接口表示配置某个特定类型 TOptions 的选项。它的实例将被视为面向默认名称 Options.DefaultName 的实例,即 string.Empty。这意味着该配置适用于默认名称的选项。
  2. IConfigureNamedOptions<TOptions>

    • IConfigureNamedOptions<TOptions> 接口扩展了 IConfigureOptions<TOptions>,允许为具有名称的选项配置提供不同的设置。它还可以实现 IConfigureOptions<TOptions> 接口,因此可以同时配置无名称和有名称的选项。
  3. IOptionsFactory<TOptions>

    • IOptionsFactory<TOptions> 接口是一个工厂接口,用于根据给定名称创建指定类型 TOptions 的选项。其默认实现具有适当地使用每个实例的逻辑,包括处理命名选项为 null 的情况,该命名选项用于面向所有命名实例,而不是特定命名实例。
  4. ConfigureAll 和 PostConfigureAll

    • ConfigureAll 和 PostConfigureAll 是一些方法,它们使用上述约定来配置或后期配置所有选项的设置,包括针对默认名称和命名选项的情况。

OptionsBuilder API

OptionsBuilder<TOptions> 是用于配置特定类型 TOptions 的选项的构建器。通过 OptionsBuilder<TOptions>,您可以对选项进行不同的配置,例如设置默认值、验证选项、注册事件处理程序等。

services.AddOptions<MyOptions>()
    .Configure(options =>
    {
        options.Option1 = "Value1";
        options.Option2 = 100;
    })
   .Configure(options =>
    {
        options.Option3 = true;
    })
    .Validate(options =>
    {
        if (options.Option2 < 0)
        {
            throw new ArgumentException("Option2 must be greater than or equal to 0.");
        }
    })
    .PostConfigure(options =>
    {
        if (options.Option1 == "InitialValue")
        {
            options.Option1 = "UpdatedValue";
        }
    });

services.AddOptions<MyConfigOptions>("MyNamedOptions")
            .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
            .ValidateDataAnnotations();

在上面的示例中,我们首先使用 AddOptions<MyOptions>() 方法注册要配置的选项类型 MyOptions。然后,我们通过 Configure 方法设置选项的初始值,通过 Validate 方法添加验证逻辑以确保选项的有效性,最后通过 PostConfigure 方法对选项进行后期配置。您可以根据自己的需求使用不同的配置方法来定制和优化选项的行为。

OptionsBuilder<TOptions> 提供了简化创建命名选项的过程的功能。通常情况下,我们可以使用 services.AddOptions<TOptions>(string optionsName) 方法注册命名选项,其中 optionsName 是选项的名称。但是,使用 OptionsBuilder<TOptions>,我们可以通过单个参数的初始调用来指定选项名称,而不必在后续调用中重复指定。

OptionsBuilder<TOptions> 提供 Configure 的重载,该重载允许使用最多五个服务来配置选项。

我们还可以使用 ConfigureOptions 方法来注册实现了 IConfigureOptions<TOptions> 接口的自定义配置类。这使得我们能够将选项的配置逻辑分离到单独的类中,更好地组织和管理代码

    // To enable unit testing
    internal static void AddMvcCoreServices(IServiceCollection services)
    {
        //
        // Options
        //
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcCoreMvcOptionsSetup>());
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IPostConfigureOptions<MvcOptions>, MvcCoreMvcOptionsSetup>());
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>());
        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IConfigureOptions<RouteOptions>, MvcCoreRouteOptionsSetup>());
……
 }

/// <summary>
/// Sets up default options for <see cref="MvcOptions"/>.
/// </summary>
internal sealed class MvcCoreMvcOptionsSetup : IConfigureOptions<MvcOptions>, IPostConfigureOptions<MvcOptions>
{
  public void Configure(MvcOptions options)
  {
      // Set up ModelBinding
      options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider());
      options.ModelBinderProviders.Add(new ServicesModelBinderProvider());
      options.ModelBinderProviders.Add(new HeaderModelBinderProvider());
      options.ModelBinderProviders.Add(new FloatingPointTypeModelBinderProvider());
  }
}

ServiceDescriptor 类的作用是定义和表示一个服务的注册信息,用于将服务添加到依赖注入容器中。它是依赖注入框架的核心组件之一,提供了灵活的机制来管理和配置服务
在上面的示例中,演示了AddMvc实现其配置项逻辑的分离。这使得配置项的逻辑可测试性和灵活性得到了极大的提高,以及更方便的管理。与下面这个示例效果相同:

  services.AddOptions<MvcOptions>()
  .Configure((options) => {
      // Set up ModelBinding
      options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider());
      options.ModelBinderProviders.Add(new ServicesModelBinderProvider());
      options.ModelBinderProviders.Add(new HeaderModelBinderProvider());
      options.ModelBinderProviders.Add(new FloatingPointTypeModelBinderProvider());
  })
  .ValidateDataAnnotations();
 

选项验证

IValidateOptions<TOptions> 接口用于定义选项验证的逻辑,它的作用是在应用程序启动时对选项进行验证。通过实现 IValidateOptions<TOptions> 接口,我们可以自定义选项的验证规则,并在应用程序启动时执行验证。

public class MyOptionsValidator : IValidateOptions<MyOptions>
{
    public ValidateOptionsResult Validate(string name, MyOptions options)
    {
        if (options.Option1 == null)
        {
            return ValidateOptionsResult.Fail("Option1 must have a value.");
        }
        
        if (options.Option2 <= 0)
        {
            return ValidateOptionsResult.Fail("Option2 must be greater than 0.");
        }
        
        return ValidateOptionsResult.Success;
    }
}

services.AddSingleton<IValidateOptions<MyOptions>, MyOptionsValidator>();

在上面的示例中,我们实现了 IValidateOptions<MyOptions> 接口,并在 Validate 方法中编写了选项的验证逻辑。如果选项的某些条件不满足,我们可以返回一个包含错误消息的 ValidateOptionsResult.Fail 对象,表示验证失败。如果选项通过验证,我们可以返回一个表示成功的 ValidateOptionsResult.Success 对象。

当应用程序启动时,验证器将自动被调用,并对选项进行验证。如果验证失败,应用程序可能会抛出异常或给出相应的警告信息,以指示选项配置的问题。通过实现 IValidateOptions<TOptions> 接口并注册验证器,我们可以在应用程序启动时验证选项的有效性,并提供有关选项配置问题的反馈。这有助于确保选项的正确设置,以使应用程序在运行时正常工作。并不必把验证逻辑放入到Validate

.Validate(options =>    {   })   //移除此方法

选项验证也支持 IValidatableObject。 若要对某个类执行类级验证,请执行以下操作:

选项验证将在第一次创建 IOptions<TOptions>IOptionsSnapshot<TOptions> 或 IOptionsMonitor<TOptions> 实现时运行。 若要立即运行选项验证,请在应用启动时调用 Program.cs 中的 ValidateOnStart

builder.Services.AddOptions<MyConfigOptions>()
    .Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
    .ValidateDataAnnotations()
    .ValidateOnStart();

 

扩展阅读

 

茴香的字有几种写法,你可知道?

//写法1:手动绑定
var leaningOptions = new LearningOptions();
Configuration.GetSection("Learning").Bind(leaningOptions);

//写法2:自动绑定
leaningOptions = Configuration.GetSection("Learning").Get<LearningOptions>();

//写法3:自动绑定 + 依赖注入
services.Configure<LearningOptions>(Configuration.GetSection("Learning"));

//写法3:命名选项+自动绑定 + 依赖注入
Services.Configure<TopItemSettings>("TopItemSettings.Year", Configuration.GetSection("TopItem:Year"));

//写法4:配置的二次加工
services.PostConfigure<LearningOptions>(options => options.Years += 1);

//PostConfigure 可用于对命名选项进行后期配置
services.PostConfigure<TopItemSettings>("Month", myOptions =>
{
    myOptions.Name = "post_configured_name_value";
    myOptions.Model = "post_configured_model_value";
});

//写法5:委托绑定
services.Configure<AppInfoOptions>(options =>
{
  options.AppName = "ASP.NET Core";
  options.AppVersion = "1.2.1";
});

Program.cs 中的选项

var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
    .CurrentValue.Option1;

选项接口

IOptions<TOptions>:

IOptionsSnapshot<TOptions>:

IOptionsMonitor<TOptions>:

全部评论



提问