在.Net Core的AppSettings /配置中处理带有句点的键名

8
考虑以下appsettings.json文件:
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",

  "NumberOfRetries": 5,
  "Option1": "abc",
  "Option2":  "def"

}

为了成功读取NumberOfRetries,可以使用以下类:
public class AppSettings
{
    public int NumberOfRetries { get; set; }
    public string Option1 { get; set; }
    public string Option2 { get; set; }
}

以下是在Startup.cs中的代码:

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

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();

        services.AddOptions();

        services.Configure<AppSettings>(Configuration);
    }

现在,假设键名为Number.Of.Retries而不是用下划线进行分隔的NumberOfRetries
如何修改AppSettings类(或方法本身)以支持这种情况?无法在属性名称中使用句点。

我非常肯定那不会起作用。那有点像有人评论说C#的变量应该像“重试次数”这样一样。生活就是如此。 - Flydog57
像 DataMember(Name=..) 这样的注释被认可吗?如果是,那么应该能够将“Foo.Bar”映射到“FooBar”。 - user2864740
3个回答

4

这可能看起来像...[ConfigurationKeyName(name: "SENDGRID_KEY")] public string SendGridKey { get; set; } - Aaron

3

好的,我想通了。

理想情况下,我只想这样使用 JsonPropertyName:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace WebAPICore
{
    public class AppSettings
    {
        [JsonPropertyName("Number.Of.Retries")]
        public int NumberOfRetries { get; set; }
        public string Option1 { get; set; }
        public string Option2 { get; set; }
    }
}

但它不起作用。为什么?他们不使用JSON解析器吗?

因此,我最终采用的解决方案看起来像这样:

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

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();

        services.AddOptions();

        services.Configure<AppSettings>(Configuration); // This maps every key that matches existing property name

        services.PostConfigure<AppSettings>(appSettings =>// This maps keys where names don't match existing property names
        {
            appSettings.NumberOfRetries = Configuration.GetValue<int>("Number.Of.Retries");
        }); 
    }

和我给出的示例有什么不同?PostConfigure - Patrick Magee
PostConfgure()加上似乎(我没有运行它)你的示例迭代了所有设置,这是不必要的。 - Joe Schmoe

1

我理解您的观点,我快速查阅了一下,有能力提供自定义逻辑来配置您的选项。我进行了一个快速的原型...

void Main()
{
    string json = @"{
      ""Logging"": {    
        ""LogLevel"": {
        ""Default"": ""Information"",
          ""Microsoft"": ""Warning"",
          ""Microsoft.Hosting.Lifetime"": ""Information""

        }
      },
      ""AllowedHosts"": ""*"",
      ""Number.Of.Retries"":  5
    }";

    using (var doc = System.Text.Json.JsonDocument.Parse(json, new JsonDocumentOptions {  AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip } ))
    {
        using(var stream = new MemoryStream())
        {
            using (var writer = new Utf8JsonWriter(stream))
            {
                doc.WriteTo(writer);
                writer.Flush();
            }       

            stream.Position = 0;

            // Usable code here
            IConfigurationRoot configuration = new ConfigurationBuilder().AddJsonStream(stream).Build();

            var services = new ServiceCollection();

            services.AddOptions<AppSettings>();

            // There is an option to configure it manually here, if it does not fit the convention
            services.Configure<AppSettings>((options) =>
            {
                options.NumberOfRetries = configuration.GetValue<int>("Number.Of.Retries");
            });

            var container = services.BuildServiceProvider();

            using (var scope = container.CreateScope())
            {
                var appSettings = scope.ServiceProvider.GetRequiredService<IOptions<AppSettings>>();

                Console.WriteLine(appSettings.Value.NumberOfRetries);
            }
        }
    }
}

public class AppSettings
{
    public int NumberOfRetries { get; set; }
}  


如果您有特定的设置模式,您可以为自己创建一个自定义设置绑定器来实现您的“约定”。我提供了一个非常基本的示例,处理设置中的“.”。

void Main()
{
    string json = @"{
      ""Logging"": {    
        ""LogLevel"": {
        ""Default"": ""Information"",
          ""Microsoft"": ""Warning"",
          ""Microsoft.Hosting.Lifetime"": ""Information""

        }
      },
      ""AllowedHosts"": ""*"",
      ""Number.Of.Retries"":  5
    }";

    using (var doc = System.Text.Json.JsonDocument.Parse(json, new JsonDocumentOptions {  AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip } ))
    {
        using(var stream = new MemoryStream())
        {
            using (var writer = new Utf8JsonWriter(stream))
            {
                doc.WriteTo(writer);
                writer.Flush();
            }       

            stream.Position = 0;

            // Usable code here
            IConfigurationRoot configuration = new ConfigurationBuilder().AddJsonStream(stream).Build();

            var services = new ServiceCollection();
            services.AddOptions<AppSettings>(); 
            services.AddSingleton<IConfiguration>(configuration);
            services.ConfigureOptions<CustomConfigureOptions>();

            var container = services.BuildServiceProvider();

            using (var scope = container.CreateScope())
            {
                var appSettings = scope.ServiceProvider.GetRequiredService<IOptions<AppSettings>>();

                Console.WriteLine(appSettings);
            }
        }
    }
}

public class AppSettings
{
    public int NumberOfRetries { get; set; }
}

public class CustomConfigureOptions : IConfigureOptions<AppSettings>
{
    private readonly IConfiguration configuration; 

    public CustomConfigureOptions(IConfiguration configuration)
    {
        this.configuration = configuration;
    }

    public void Configure(AppSettings options)
    {
        foreach(var pair in configuration.AsEnumerable())
        {
            foreach(var property in typeof(AppSettings).GetProperties())
            {
                if (property.Name.Equals(pair.Key.Replace(".", "")))
                {
                    property.SetValue(options, configuration.GetValue(property.PropertyType, pair.Key));
                }
            }
        }
    }
}



这很可能是唯一可行的解决方案,但您不认为它有点丑陋吗:D?如果我有几个类似的属性,我将不得不显式地将它们映射到每个属性。我真心希望有更好的方法。 - Joe Schmoe
1
有一个替代方案,但我不确定它是否值得。请查看我提供的详细答案 :) - Patrick Magee
1
是的,出于这个特定的原因,您可能会喜欢我所给出的扩展答案,它展示了一种更好的做法。 - Patrick Magee
默认配置处理使用Text.Json来解析(或Json.NET)吗?如果是这样,像DateMember(Name = ..)这样的注释是否被尊重? - user2864740
在.NET5中,appsettings.json绑定行为类似:实际上会忽略[JsonPropertyName("...")]属性,来自appsettings.json文件的值将加载到具有相同名称(不区分大小写)的类属性中。 - apdevelop
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接