总所周知,配置信息是应用程序可变化的设置信息,存放于配置文件中,开发人员可以使用配置文件来更改应用的设置。.net core提供了多种配置方式,例如json文件配置,注册表配置、环境配置,xml文件配置等。其中大家常用的是一个json配置文件方式,即每个应用都有一个appsettings.json配置文件,存放所有的配置信息。
这里,我还有个需要说明的地方,就是当前系统的配置过于庞大了,我建议尽量采用约定方式,就是约定优于配置。例如所有的MVC Controller都以Controller为结尾就是一种约定优于配置的例子。
但这种配置方式存在重复配置问题。例如有三个应用项目访问某数据库,该数据库的数据库连接串将在这三个应用项目的配置文件中分别存放,当数据库的服务器地址、用户、口令等变化时,均需要在这三个项目中修改,随着系统的增多,极易造成漏改的情况。同样的,在云环境下,应用项目可以部署动态分配、扩展的计算资源中(多台虚拟服务器),修改一个配置项就需要同时修改各个服务器中的配置文件,也一样的容易造成漏改的情况。
为了解决这个问题,我们采用了多级配置文件方式,将公用部分配置信息提取出来,存放到一个独立的公共配置文件中,这三个项目均访问公共配置文件以获取数据库连接串。公共配置文件方式可以解决目前存在的问题。然而,在实际运行过程中又发现了新的问题,就是运行过程中发现了程序异常,技术人员经常需要查看配置信息,或者需要修改配置,而客户安全要求比较高(对于客户技术人员的能力就不吐槽了),根本不允许直接访问服务器,排查问题非常困难。
因此,我们最终采用数据库方式存放配置信息。在数据库中增加一个配置信息表,统一存放公共的配置信息,并增加相应的管理界面进行维护。各系统均从同一数据库中获取公共配置信息,完美的解决了重复性配置问题。
首先是公用的配置基类:
1 public abstract class ConfigInfo 2 { 3 public abstract string SectionName { get; } 4 5 public abstract void RegisterOptions(IServiceCollection services, IConfigurationRoot root); 6 }
SectionName是配置的名称,RegisterOptions的做法是把配置信息(请参考Options模式)注册到DI中,.net core的缺省注册容器就是IServiceCollection。所有的配置类,例如缓存配置、数据库配置、日志配置等都必须继承该类。例如缓存配置实现类如下:
1 public sealed class CachingConfigInfo : ConfigInfo 2 { 3 /// <summary> 4 /// 缓存滑动窗口时间(分钟) 5 /// </summary> 6 public int DefaultSlidingTime { get; set; } 7 8 /// <summary> 9 /// 类型 10 /// </summary> 11 public string Type { get; set; } 12 13 /// <summary> 14 /// 参数 15 /// </summary> 16 public List<CacheServer> Servers { get; set; } 17 18 public override string SectionName 19 { 20 get 21 { 22 return "MicroStrutLibrary:Caching"; 23 } 24 } 25 26 public override void RegisterOptions(IServiceCollection services, IConfigurationRoot root) 27 { 28 services.Configure<CachingConfigInfo>(root.GetSection(SectionName)); 29 } 30 }
其次,配置源基类。配置源是指公共配置(例如缓存、数据库、日志等)存放的位置,例如数据库或者Json文件等。在每个应用项目的appsettings.json中建立一个配置节,存放当前公共配置存放的位置。例如数据库方式和Json文件方式的配置源在appsettings.json文件中的写法如下:
1 "ConfigurationSource": { 2 "StorageMode": "JsonFile", 3 "Parameter": "D:\Programs\OSChina\MicroStrutLibrary\Client\MicroStrutLibrary.Client.SampleWebApp\applibrary.json" 4 } 5 6 ---------- 7 "ConfigurationSource": { 8 "StorageMode": "Database", 9 "Parameter": "Data Source=XXXXXX;Initial Catalog=MicroStrutLibrary;User Id=OperUser;Password=XXXX;MultipleActiveResultSets=true;Persist Security Info=true" 10 }
配置源基类的代码如下:
1 public abstract class ConfigSource 2 { 3 private const string SourceSectionName = "MicroStrutLibrary:ConfigurationSource"; 4 private const string DefaultStorageMode = "JsonFile"; 5 private const string DefaultParameter = "appsettings.json"; 6 7 protected string _Parameter; 8 9 public static ConfigSource GetConfigSource() 10 { 11 IConfiguration configuration = new ConfigurationBuilder() 12 .SetBasePath(Directory.GetCurrentDirectory()) 13 .AddJsonFile("appsettings.json") 14 .Build(); 15 16 IConfigurationSection section = configuration.GetSection(SourceSectionName); 17 18 string storageMode = section["StorageMode"] ?? DefaultStorageMode; 19 string parameter = section["Parameter"] ?? DefaultParameter; 20 21 TypeNameHelperInfo info = TypeNameHelper.GetTypeNameHelperInfo<ConfigSource>(storageMode); 22 23 return ReflectionHelper.CreateInstance(info.Type, new object[] { parameter }) as ConfigSource; 24 } 25 26 public ConfigSource(string parameter) 27 { 28 this._Parameter = parameter; 29 } 30 31 public abstract IConfigurationRoot GetConfigurationRoot(); 32 }
静态方法GetConfigSource,从应用的本地配置文件appsettings.json中获取配置源设置,然后创建数据库或者Json文件等数据源。TypeNameHelper的内容,请参照“反射工具”。
抽象方法GetConfigurationRoot需要每个具体子类实现,主要作用是从配置源中获取所有配置信息,生成配置信息的IConfiguraionRoot对象。
下面是Json配置源的具体实现:
1 [TypeName("JsonFile", "Json配置文件")] 2 public class JsonConfigSource : ConfigSource 3 { 4 public JsonConfigSource(string parameter) : base(parameter) 5 { 6 } 7 8 public override IConfigurationRoot GetConfigurationRoot() 9 { 10 //JsonConfigurationSource source = new JsonConfigurationSource(); 11 //source.Path = _Parameter; 12 13 //ConfigurationBuilder builder = new ConfigurationBuilder(); 14 //builder.Add(source); 15 16 ConfigurationBuilder builder = new ConfigurationBuilder(); 17 builder.AddJsonFile(_Parameter); 18 19 return builder.Build(); 20 } 21 }
Json方式配置源的大体写法:
{ "MicroStrutLibrary": { "Caching": { "DefaultSlidingTime": 20, "Type": "MicroStrutLibrary.Infrastructure.Core.Caching.DefaultCacheHandler, MicroStrutLibrary.Infrastructure.Core" }, "Database": { "ConnectionStrings": [ { "Name": "MicroStrutLibrary", "ConnectionString": "Data Source=XXXX;Initial Catalog=MicroStrutLibrary;User Id=OperUser;Password=XXXX;MultipleActiveResultSets=true;Persist Security Info=true", "ProviderName": "System.Data.SqlClient" } ], "Providers": [ { "Name": "System.Data.SqlClient", "Type": "MicroStrutLibrary.Infrastructure.Core.Data.Entity.SqlServerDbContextOptionsBuilderProvider, MicroStrutLibrary.Infrastructure.Core.Data.Entity" } ] } } }
最后,在Startup.cs的ConfigureServices中增加类似services.AddConfiguration()的语句,注册配置源的方式。AddConfiguration是一个扩展的方法,具体实现如下:
1 public static IServiceCollection AddConfiguration(this IServiceCollection services) 2 { 3 if (services == null) 4 { 5 throw new ArgumentNullException(nameof(services)); 6 } 7 8 ConfigSource configSource = ConfigSource.GetConfigSource(); 9 10 IConfigurationRoot root = configSource.GetConfigurationRoot(); 11 12 IEnumerable<Type> list = ReflectionHelper.GetSubTypes<ConfigInfo>(); 13 foreach (Type type in list) 14 { 15 ConfigInfo configInfo = Activator.CreateInstance(type) as ConfigInfo; 16 17 configInfo.RegisterOptions(services, root); 18 } 19 20 return services; 21 }
大体流程是,获取配置源信息,然后从配置源中获取ConfigurationRoot,获取所有的ConfigInfo的子类,创建并注册到DI容器中。GetSubTypes内容,请参照“反射工具”