ASP.NET 核心通过 HTTP 接收自定义配置以配置 Kestrel

ASP.NET Core receive custom config via HTTP to configure Kestrel

这感觉像是一个简单的问题,但在我所有的搜索中,我仍然没有找到适合我的解决方案。可能是另一种找不到我想要的东西的情况,因为我没有搜索正确的 'thing',但我们在这里..

我有一个 C# Web API 程序,我想在其中从配置对象配置 kestrel 服务器。

我通过 rest 调用将此配置接收到我的服务中,进入 CustomConfig 对象。我可以在 Program.csStartup.cs 中获取此配置对象,但由于我不想重复自己并进行额外的调用,所以我不想在这两个地方都这样做。

我的偏好是在 Startup.cs 中获取配置,因为这是我的其余配置代码所在的位置,也是我已经在使用我的 CustomConfig 对象的位置。但是,我找不到一种方法来配置 kestrel 服务器以使用我给它的证书(在 Startup.cs 中),我也看不到将此配置从 Startup.cs 注入 Startup.cs 的方法=13=].

在其他项目中,我将 PFX 文件的位置作为环境变量传递:ASPNETCORE_Kestrel__Certificates__Default__Path(在这种情况下,无需额外的代码配置,一切正常),但在这个项目中,所有配置都必须通过 rest 检索打电话,所以这不是这里的一个选项。

我目前拥有所有东西 运行,但只能通过调用 rest 两次来获取配置。当前配置 kestrel 的实现是将 PFX 存储在 CustomConfig 中作为 base64 字符串,并在 Program.cs:

中配置
public static IHostBuilder CreateHostBuilder(string[] args)
        {
            return Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    CustomConfig config = CustomConfig() // <- I receive config here
                    webBuilder.UseStartup<Startup>();
                    webBuilder.UseKestrel(options =>
                    {
                       options.ConfigureHttpsDefaults(d =>
                       {
                           byte[] pfxBytes = Convert.FromBase64String(config.Base64PFXBytes);
                           d.ServerCertificate = new X509Certificate2(pfxBytes, "sslKey");
                       });
                    });
                });
        }

总结一下..

所以我正在寻求帮助:

希望这是有道理的。为了清楚起见,欢迎提出任何和所有解决方案/其他问题!

提前致谢!

ASP.NET 核心使用 IConfiguration 接口抽象配置。不细说,收集 来自各种 IConfigurationSource 的配置并将它们叠加在一起,这让我们可以覆盖 通过使用相同的键定义在另一个源中定义的设置。

1。实施 IConfigurationSource

让我们实施一个 IConfigurationSource。我们可以使用 ConfigurationSource 摘要 class 作为我们的起点。出色地 使用内存中实现,然后切换到远程源。

class RemoteConfigurationSource : IConfigurationSource
{
    public IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        return new RemoteConfigurationProvider(_options);
    }

    private class RemoteConfigurationProvider : ConfigurationProvider
    {
        public override void Load()
        {
            // TODO: fetch data from the API
            var remoteConfig = new Dictionary<string, string>
            {
                { "CertificateOptions:PfxBase64", "MIIKkQIBAz....gfQ" },
                { "CertificateOptions:Password", "secret" },
            };
            Data = remoteConfig;
        }
    }
}

然后将其添加到 ConfigureHostConfiguration 回调中的配置生成器:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureHostConfiguration(builder =>
            {
                // add new source
                builder.AddRemoteConfiguration();
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>().UseKestrel((context, options) =>
                {
                    var certificateOptions = context.Configuration
                        .GetSection(KestrelCertificateOptions.ConfigurationKey)
                        .Get<KestrelCertificateOptions>();
                    options.ConfigureHttpsDefaults(adapterOptions =>
                        adapterOptions.ServerCertificate = certificateOptions.Certificate);
                });
            });
}

public static class ConfigurationBuilderExtensions
{
    public static IConfigurationBuilder AddRemoteConfiguration(this IConfigurationBuilder builder) =>
        builder.Add(new RemoteConfigurationSource());
}

class KestrelCertificateOptions
{
    public const string ConfigurationKey = "CertificateOptions";
    public string PfxBase64 { get; set; }
    public string Password { get; set; }
    public X509Certificate2 Certificate => new X509Certificate2(Convert.FromBase64String(PfxBase64), Password);
}

当我们 运行 应用程序时,ASP.NET 核心将加载并使用我们的内存配置。

2。从 API

获取配置数据

现在让我们从远程 API 获取配置。它需要 return 配置值,部分名称以分隔 带有冒号 :。这是与 JSON 相同的配置,在 CertificateOptions 部分下归档:

{
  "CertificateOptions:PfxBase64": "MII....oCAgfQ",
  "CertificateOptions:Password": "secret"
}

假设 API 包装 returns 此数据包装为:

{
  "Application": "MyApp",
  "LastChanged": "2021-08-09 14:38:00",
  "Data": {
    "CertificateOptions:PfxBase64": "MIIK...oCAgfQ",
    "CertificateOptions:Password": "secret"
  }
}

所以我们在获取数据时只需要考虑 Data 键。

class RemoteConfigurationSource : IConfigurationSource
{
    public IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        return new RemoteConfigurationProvider();
    }

    private class RemoteConfigurationProvider : ConfigurationProvider
    {
        public override void Load()
        {
            // We cannot await this method, so have to do sync-over-async. 
            // Not an issue, because it's a one-time thing.
            var result = LoadRemoteConfig().GetAwaiter().GetResult();
            Data = result.Data;
        }

        private async Task<RemoteConfigResult> LoadRemoteConfig()
        {
            // We cannot use IHttpClientFactory here, since ServiceProvider isn't even built yet.
            using var httpClient = new HttpClient();
            // ... add headers, token to request
            return await httpClient.GetFromJsonAsync<RemoteConfigResult>("https://example.com/path/to/json");
        }
    }

    private class RemoteConfigResult
    {
        public Dictionary<string, string> Data { get; set; }
    }
}

为了稍微清理一下,我们可以将 URL 和其他凭据移动到 appsettings.json:

{
  "Logging": {
    /*...*/
  },
  "RemoteConfiguration": {
    "Url": "https://jsonkeeper.com/b/B78I",
    "ApplicationId": "myconfigappid",
    "Secret": "myconfigapisecret"
  }
}

然后构建一个临时的 IConfiguration 根据需要添加尽可能多的源,然后获取这些值:

// Read credentials from appsettings.json
var remoteConfigurationOptions = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: false)
    .Build()
    .GetSection(RemoteConfigurationOptions.ConfigurationKey)
    .Get<RemoteConfigurationOptions>();
    
public class RemoteConfigurationOptions
{
    public const string ConfigurationKey = "RemoteConfiguration";
    public string Url { get; set; }
    public string ApplicationId { get; set; }
    public string Secret { get; set; }
}

然后将这个对象传递给我们的配置源,配置源又将它传递给配置提供者