如何从 ASP.Net Core 配置文件合并多个数组?

Łuk*_*ski 6 c# asp.net-core

我想在我的应用程序中动态加载和注册服务。为此,我需要能够从解决方案中的不同项目加载配置文件,并将它们中的值合并到单个 json 数组中。不幸的是,默认情况下 ASP.Net Core 配置会覆盖值。

我使用以下代码注册文件(Program.cs 文件的一部分):

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((webHostBuilderContext, configurationbuilder) =>
            {
                var env = webHostBuilderContext.HostingEnvironment;
                configurationbuilder.SetBasePath(env.ContentRootPath);
                configurationbuilder.AddJsonFile("appsettings.json", false, true);

                var path = Path.Combine(env.ContentRootPath, "App_Config\\Include");

                foreach(var file in Directory.EnumerateFiles(path, "*.json",SearchOption.AllDirectories))
                {
                    configurationbuilder.AddJsonFile(file, false, true);
                }
                configurationbuilder.AddEnvironmentVariables();
            })
            .UseStartup<Startup>();
Run Code Online (Sandbox Code Playgroud)

该代码*.jsonApp_Config\Include目录中搜索所有带有扩展名的文件,并将它们全部添加到配置生成器中。

文件的结构如下所示:

{
  "ServicesConfiguration": {
    "Services": [
      {
        "AssemblyName": "ParsingEngine.ServicesConfigurator, ParsingEngine"
      }
    ]
  }
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,我得到了主要部分,ServicesConfiguration然后Services用具有一个属性的对象数组AssemblyName

要读取这些值,我使用ServicesConfiguration带列表的类:

public class ServicesConfiguration
    {
        public List<ServiceAssembly> Services { get; set; }
    }
Run Code Online (Sandbox Code Playgroud)

该列表使用ServiceAssembly类:

public class ServiceAssembly
    {
        public string AssemblyName { get; set; }
    }
Run Code Online (Sandbox Code Playgroud)

要加载我IOptions在构造函数级别 (DI)使用的配置:

Microsoft.Extensions.Options.IOptions<ServicesConfiguration> servicesConfiguration,
Run Code Online (Sandbox Code Playgroud)

并且配置似乎已加载 - 但文件中的值并未合并,而是被上次找到的文件覆盖。

任何想法如何解决这个问题?

jpg*_*ssi 5

所以你对我在评论中的意思有一个想法,这是一个潜在的答案

由于您必须从不同的项目加载不同的“配置”文件并对它们应用一些合并逻辑,因此我将避免使用“默认”配置系统将JSON文件加载到应用程序中。相反,我只会自己做。所以:

  1. 读取 JSON 并将其反序列化为类型并将其保留在列表中
  2. 浏览包含所有配置的列表并应用您的合并逻辑
  3. 将单曲注册ServicesConfigurationSingleton
  4. 删除您Program.cs加载自定义JSON文件的代码

你可以这样做:

ServicesRootConfiguration (新类,能够反序列化json)

public class ServicesRootConfiguration
{
    public ServicesConfiguration ServicesConfiguration { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

Startup.cs

public class Startup
{
    private readonly IHostingEnvironment _hostingEnvironment;

    public Startup(IConfiguration configuration, IHostingEnvironment env)
    {
        Configuration = configuration;
        _hostingEnvironment = env;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // other configuration omitted for brevity

        // build your custom configuration from json files
        var myCustomConfig = BuildCustomConfiguration(_hostingEnvironment);

        // Register the configuration as a Singleton
        services.AddSingleton(myCustomConfig);
    }

    private static ServicesConfiguration BuildCustomConfiguration(IHostingEnvironment env)
    {
        var allConfigs = new List<ServicesRootConfiguration>();

        var path = Path.Combine(env.ContentRootPath, "App_Config");

        foreach (var file in Directory.EnumerateFiles(path, "*.json", SearchOption.AllDirectories))
        {
            var config = JsonConvert.DeserializeObject<ServicesRootConfiguration>(File.ReadAllText(file));
            allConfigs.Add(config);
        }

        // do your logic to "merge" the each config into a single ServicesConfiguration
        // here I simply select the AssemblyName from all files.
        var mergedConfig = new ServicesConfiguration
        {
            Services = allConfigs.SelectMany(c => c.ServicesConfiguration.Services).ToList()
        };

        return mergedConfig;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在您Controller通常通过 DI 获取实例。

public class HomeController : Controller
{
    private readonly ServicesConfiguration _config;

    public HomeController(ServicesConfiguration config)
    {
        _config = config ?? throw new ArgumentNullException(nameof(config));
    }
}
Run Code Online (Sandbox Code Playgroud)

使用这种方法,您最终会得到与通常注册IOptions. 但是,您可以避免依赖它并避免使用 uggly .Value(urgh)。更好的是,您可以将其注册为接口,以便在测试/模拟期间使您的生活更轻松。