如何在 ASP.NET Core 中自定义配置绑定

Jon*_*Jon 7 c# configuration multi-tenant asp.net-core

我有一个多租户 ASP.NET Core Web 应用程序。当前的租户模型是每个租户都有一个单独的 Web 应用程序和 SQL 数据库。我正在尝试重新构建它,以便单个 Web 应用程序为多个租户提供服务(但为每个租户维护一个单独的数据库)。我一直在关注这一系列的博客文章,但在配置方面遇到了一些障碍。

该应用大量使用 ASP.NET Core 配置系统,并有一个自定义 EF Core 提供程序,可从数据库中获取配置值。如果可能的话,我想保留它,撕掉并替换为其他东西(在数百个地方使用了数十个配置设置)将是一项艰巨的工作。

现有代码非常标准:

public class MyAppSettings
{
    public string FavouriteColour { get; set; }
    public int LuckyNumber { get; set; }
}

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

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOptions();
        services.Configure<MyAppSettings>(Configuration.GetSection("MyAppSettings"));
        // etc....
    }
}

// custom EF Core config provider wired up in Program.Main, but that doesn't actually seem relevant
Run Code Online (Sandbox Code Playgroud)

我已经更新了我们的自定义提供程序,以便它从所有已知的租户数据库中获取所有配置值,并将它们全部添加到配置系统中,并以租户标识符为前缀,因此从 n 个不同数据库中获取的所有配置值的列表可能看起来像这样:

Key                                       Value
===============================================
TenantABC:MyAppSettings:FavouriteColour   Green
TenantABC:MyAppSettings:LuckyNumber       42
TenantDEF:MyAppsettings:FavouriteColour   Blue
TenantDEF:MyAppSettings:LuckyNumber       37
...
TenantXYZ:MyAppSettings:FavouriteColour   Yellow
TenantXYZ:MyAppSettings:LuckyNumber       88
Run Code Online (Sandbox Code Playgroud)

我希望能够做的是以某种方式自定义绑定配置的方式,以便它解析当前请求的租户,然后使用适当的值,例如 abc.myapp.com 上的请求将观察配置值“绿色”和“42”等,不必更改注入IOptionsMonitor<AppSettings>(或IOptionsSnapshot等)的所有依赖位置。链接的博客系列有一篇关于配置文章,其中涵盖了一些我希望我最终会在缓存等方面遇到的问题,但它似乎无法满足为不同租户使用完全不同设置的这种情况。从概念上讲,它似乎很简单,但我一直无法找到正确的挂钩位置。请帮忙!

Ser*_*giy 5

这是一个想法(但是尚未测试)。您可以保存传递给 Startup 类的构造函数的默认 IConfiguration 实例,然后在 DI 中注册您自己的 IConfiguration 实现,该实现将使用该默认实例和 HttpContextAccessor(以获取当前租户)。

所以代码看起来像:


public class Startup 
{
    private IConfiguration _defaultConfig;

    public Startup(IConfiguration configuration, IWebHostEnvironment env)
    {
        _defaultConfig = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        .   .   .   .
        services.AddScoped<IConfiguration>(serviceProvider => {
            var httpContextAccessor = 
                  serviceProvider.GetService<IHttpContextAccessor>();
            return new MyConfig(_defaultConfig, httpContextAccessor);
        });
    }

    .   .   .   .
}

public class MyConfig : IConfiguration
{
    private readonly IConfiguration _defaultConfig;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public MyConfig(IConfiguration defaultConfig, IHttpContextAccessor httpContextAccessor) 
    {
        _defaultConfig = defaultConfig;
        _httpContextAccessor = httpContextAccessor;
    }

    public string this[string key] {
        get {
            var tenantId = GetTenantId();
            return _defaultConfig[tenantId + ":" + key];
        }
        set {
            var tenantId = GetTenantId();
            _defaultConfig[tenantId + ":" + key] = value;
        }
    }

    protected virtual string GetTenantId()
    { 
        //this is just an example that supposes that you have "TenantId" claim associated with each user
        return _httpContextAccessor.HttpContext.User.FindFirst("TenantId").Value; ;
    }

    public IEnumerable<IConfigurationSection> GetChildren()
    {
        return _defaultConfig.GetChildren();
    }

    public IChangeToken GetReloadToken()
    {
        return _defaultConfig.GetReloadToken();
    }

    public IConfigurationSection GetSection(string key)
    {
        var tenantId = GetTenantId();
        return _defaultConfig.GetSection(tenantId + ":" + key);
    }
}
Run Code Online (Sandbox Code Playgroud)