作为Docs Security Suit产品开发的一部分,我们面临着在数据库和配置中存储许多不同类型的应用程序设置的任务。 并且还可以方便地读取和写入它们。 在这里,IConfiguration接口将为我们提供帮助,特别是因为它通用且易于使用,它将使您可以将各种设置存储在一个地方
定义任务
ASP.Net Core应用程序现在可以通过IConfiguration界面使用应用程序设置。 关于与他合作的文章很多。 本文将介绍使用IConfiguration存储应用程序设置的经验,例如用于连接LDAP服务器,SMTP服务器等的设置。 目标是配置用于与应用程序配置一起使用以与数据库一起使用的现有机制。 在本文中,您将找不到使用该接口的标准方法的描述。
该应用程序体系结构是基于DDD与CQRS一起构建的。 此外,我们知道IConfiguration接口对象将所有设置存储为键值对。 因此,我们首先以这种形式描述了域设置的某些本质:
public class Settings: Entity { public string Key { get; private set; } public string Value { get; private set; } protected Settings() { } public Settings(string key, string value) { Key = key; SetValue(value); } public void SetValue(string value) { Value = value; } }
该项目使用EF Core作为ORM。 迁移负责FluentMigrator。
在我们的上下文中添加一个新实体:
public class MyContext : DbContext { public MyContext(DbContextOptions options) : base(options) { } public DbSet<Settings> Settings { get; set; } … }
接下来,对于我们的新实体,我们需要描述EF的配置:
internal class SettingsConfiguration : IEntityTypeConfiguration<Settings> { public void Configure(EntityTypeBuilder<Settings> builder) { builder.ToTable("Settings"); } }
并为此实体编写一个迁移:
[Migration(2019020101)] public class AddSettings: AutoReversingMigration { public override void Up() { Create.Table("Settings") .WithColumn(nameof(Settings.Id)).AsInt32().PrimaryKey().Identity() .WithColumn(nameof(Settings.Key)).AsString().Unique() .WithColumn(nameof(Settings.Value)).AsString(); } }
提到的IConfiguration在哪里?
我们使用IConfigurationRoot接口
我们的项目有一个基于ASP.NET Core MVC构建的api应用程序。 默认情况下,我们将IConfiguration用于应用程序设置的标准存储,例如,连接到数据库:
public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { void OptionsAction(DbContextOptionsBuilder options) => options.UseSqlServer(Configuration.GetConnectionString("MyDatabase")); services.AddDbContext<MyContext>(OptionsAction); ... }
这些设置默认情况下存储在环境变量中:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, config) => { config.AddEnvironmentVariables(); })
根据这个想法,我们可以使用该对象存储预期的设置,但是它们将与应用程序本身的常规设置相交(如上所述,连接到数据库)
为了在DI中分离连接的对象,我们决定使用IConfigurationRoot子接口:
public void ConfigureServices(IServiceCollection services) { services.AddScoped<IConfigurationRoot>(); ... }
当您将其连接到我们的服务容器时,我们可以安全地使用单独配置的设置对象,而不会干扰应用程序本身的设置。
但是,容器中的对象对域中的本质以及如何使用数据库一无所知。

我们描述了新的配置提供者
回想一下,我们的任务是将设置存储在数据库中。 为此,您需要描述从ConfigurationProvider继承的新配置提供程序IConfigurationRoot。 为了使新提供程序正常工作,我们必须描述从数据库中读取的方法-Load()和写入数据库的方法-Set():
public class EFSettingsProvider : ConfigurationProvider { public EFSettingsProvider(MyContext myContext) { _myContext = myContext; } private MyContext _myContext; public override void Load() { Data = _myContext.Settings.ToDictionary(c => c.Key, c => c.Value); } public override void Set(string key, string value) { base.Set(key, value); var configValues = new Dictionary<string, string> { { key, value } }; var val = _myContext.Settings.FirstOrDefault(v => v.Key == key); if (val != null && val.Value.Any()) val.SetValue(value); else _myContext.Settings.AddRange(configValues .Select(kvp => new Settings(kvp.Key, kvp.Value)) .ToArray()); _myContext.SaveChanges(); } }
接下来,您需要为实现IConfigurationSource的配置描述一个新来源:
public class EFSettingsSource : IConfigurationSource { private DssContext _dssContext; public EFSettingSource(MyContext myContext) { _myContext = myContext; } public IConfigurationProvider Build(IConfigurationBuilder builder) { return new EFSettingsProvider(_myContext); } }
为了简单起见,将扩展添加到IConfigurationBuilder:
public static IConfigurationBuilder AddEFConfiguration( this IConfigurationBuilder builder, MyContext myContext) { return builder.Add(new EFSettingSource(myContext)); }
现在,我们可以在将对象连接到DI的地方指定我们描述的提供者:
public void ConfigureServices(IServiceCollection services) { services.AddScoped<IConfigurationRoot>(provider => { var myContext = provider.GetService<MyContext>(); var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddEFConfiguration(myContext); return configurationBuilder.Build(); }); ... }
我们与新供应商的合作为我们带来了什么?
IConfigurationRoot示例
首先,让我们定义一个特定的Dto模型,该模型将广播到我们的应用程序的客户端,例如,存储用于连接ldap的设置:
public class LdapSettingsDto { public int Id { get; set; } public string UserName { get; set; } public string Password { get; set; } public string Address { get; set; } }
IConfiguration可以从“框”中很好地读写对象的一个实例。 为了与馆藏合作,需要进行一些小的改进。
为了存储几个相同类型的对象,我们为IConfigurationRoot编写了扩展名:
public static void SetDataFromObjectProperties(this IConfigurationRoot config, object obj, string indexProperty = "Id") {
因此,我们可以使用一些设置实例。
将设置写入数据库的示例
如上所述,我们的项目使用CQRS方法。 为了编写设置,我们描述了一个简单的命令:
public class AddLdapSettingsCommand : IRequest<ICommandResult> { public LdapSettingsDto LdapSettings { get; } public AddLdapSettingsCommand(LdapSettingsDto ldapSettings) { LdapSettings = ldapSettings; } }
然后是我们团队的经理:
public class AddLdapSettingsCommandHandler : IRequestHandler<AddLdapSettingsCommand, ICommandResult> { private readonly IConfigurationRoot _settings; public AddLdapSettingsCommandHandler(IConfigurationRoot settings) { _settings = settings; } public async Task<ICommandResult> Handle(AddLdapSettingsCommand request, CancellationToken cancellationToken) { try { _settings.SetDataFromObjectProperties(request.LdapSettings); } catch (Exception ex) { return CommandResult.Exception(ex.Message, ex); } return await Task.Run(() => CommandResult.Success, cancellationToken); } }
结果,我们可以根据所描述的逻辑将ldap设置的数据写到数据库中。
在数据库中,我们的设置如下所示:

从数据库读取设置的示例
要读取ldap设置,我们将编写一个简单的查询:
public class GetLdapSettingsByIdQuery : IRequest<LdapSettingsDto> { public int Id { get; } public GetLdapSettingsByIdQuery(int id) { Id = id; } }
然后是我们请求的处理程序:
public class GetLdapSettingsByIdQueryHandler : IRequestHandler<GetLdapSettingsByIdQuery, LdapSettingsDto> { private readonly IConfigurationRoot _settings; public GetLdapSettingsByIdQueryHandler(IConfigurationRoot settings) { _settings = settings; } public async Task<LdapSettingsDto> Handle(GetLdapSettingsByIdQuery request, CancellationToken cancellationToken) { var ldapSettings = new List<LdapSettingsDto>(); _settings.Bind(nameof(LdapSettingsDto), ldapSettings); var ldapSettingsDto = ldapSettings.FirstOrDefault(ls => ls.Id == request.Id); return await Task.Run(() => ldapSettingsDto, cancellationToken); } }
从示例中可以看到,使用Bind方法,我们使用数据库中的数据填充ldapSettings对象-以名称LdapSettingsD来确定我们需要用来接收数据的键(部分),然后调用提供程序中描述的Load方法。
接下来呢?
然后,我们计划将应用程序中的各种设置添加到共享存储库中。
我们希望我们的解决方案对您有用,并且您将与我们分享您的问题和意见。