第17章 配置类的反射方式实例化、单例和依赖注入
1 Core .Configuration.IConfig
using Newtonsoft.Json;
namespace Core.Configuration
{
/// <summary>
/// 【配置--接口】
/// <remarks>
/// 摘要:
/// 通过继承于该接口的具体实现类中的属性成员实例实现与“appsettings.json”文件中1具指定的JSON根节点中的相关数据进行读写操作,为当程序中指定中间件的实例化提供数据支撑。
/// </remarks>
/// </summary>
public interface IConfig
{
/// <summary>
/// 【名称】
/// <remarks>
/// 摘要:
/// 从“appsettings.json”文件中获取1组JSON键/值对中的1个指定且唯一的JSON根节点的字符串值。
/// JsonIgnore特性:
/// 当前“DataConfig”类的实例通过JSON中的方法进行序列化时,该属性成员及其实例值将不会被序列化,更不会被持久化到“appsettings.json”文件中,
/// 即该属性成员实例设定1个指定且唯一的JSON根节点,而不是在“appsettings.json”文件指定的JSON根节点中设定: "Name": GetType().Name JSON键/值对。
/// </remarks>
/// </summary>
[JsonIgnore]
string Name => GetType().Name;
///<summary>
/// 【获取顺序】
/// <remarks>
/// 摘要:
/// 在对所有继承于“IConfig”接口的具体实现类的所有实例进行排序操作时,设定指定具体实现类的实例在所有实例中的次序,默认值:1。
/// </remarks>
/// <returns>
/// 返回:
/// 指定具体实现类的实例在所有实例中的排序的顺序值:1。
/// </returns>
/// </summary>
public int GetOrder() => 1;
}
}
2 Core .Configuration.AppSettings
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Core.Configuration
{
/// <summary>
/// 【应用配置--类】
/// <remarks>
/// 摘要:
/// 把当前程序中所有继承了“IConfig”接口的具体实现类的实例以键/值对的形式存储到当前类的的变量和属性字典成员实例中。
/// </remarks>
/// </summary>
public class AppSettings
{
#region 变量--私有/保护
/// <summary>
/// 【配置集】
/// <remarks>
/// 摘要:
/// 设置字典变量成员实例,该实例以键/值对的形式存储着所有继承了“IConfig”接口的具体实现类的实例。
/// </remarks>
/// </summary>
private readonly Dictionary<Type, IConfig> _configurations = new();
#endregion
#region 拷贝构造方法
/// <param name="configurations">列表接口实例,该实例存储着所有继承于“IConfig”接口的具体实现类的实例</param>
/// <summary>
/// 【静态构造方法】
/// <remarks>
/// 摘要:
/// 通过该静态构造方法,对当前类中的电子邮箱正则表达式变量成员进行实例化。
/// </remarks>
/// </summary>
public AppSettings(IList<IConfig> configurations = null)
{
_configurations = configurations
?.OrderBy(config => config.GetOrder())
?.ToDictionary(config => config.GetType(), config => config)
?? new Dictionary<Type, IConfig>();
}
#endregion
#region 属性
/// <summary>
/// 【配置】
/// <remarks>
/// 摘要:
/// 获取/设置字典属性成员实例,该实例以键/值对的形式存储着所有继承了“IConfig”接口的具体实现类的实例。
/// 说明:
/// 该字典属性成员实例所存储到键/值对中的值,经过JSON编码格式的格式化过后的字符串。
/// </remarks>
/// </summary>
[JsonExtensionData]
public Dictionary<string, JToken> Configuration { get; set; }
#endregion
#region 方法
/// <typeparam name = "TConfig"> 泛型类型实例(这里指定:所有继承于“IConfig”接口的具体实现类)。</typeparam>
/// <summary>
/// 【获取】
/// <remarks>
/// 摘要:
/// 获取1个指定的继承于“IConfig”接口的具体实现类的类型实例。
/// </remarks>
/// <returns>
/// 返回:
/// 1个指定的继承于“IConfig”接口的具体实现类的类型实例。
/// </returns>
/// </summary>
public TConfig Get<TConfig>() where TConfig : class, IConfig
{
if (_configurations[typeof(TConfig)] is not TConfig config)
throw new NopException($"当前程序中没有定义继承于 '{typeof(TConfig)}' 的类");
return config;
}
/// <param name="configurations">列表接口实例,该实例存储着所有继承于“IConfig”接口的具体实现类的实例</param>
/// <summary>
/// 【更新】
/// <remarks>
/// 摘要:
/// 把继承于“IConfig”接口的具体实现类的实例,以键/值对的形式存储到当前类的字典变量成员实例中。
/// </remarks>
/// </summary>
public void Update(IList<IConfig> configurations)
{
foreach (var config in configurations)
{
_configurations[config.GetType()] = config;
}
}
#endregion
}
}
4 Core .Configuration.AppSettingsHelper
using System.Text;
using Core.Infrastructure;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Core.Configuration
{
/// <summary>
/// 【应用配置助手--类】
/// <remarks>
/// 摘要:
/// 通过当前类中的成员方法把当前程序中所有继承了“IConfig”接口的具体实现类的实例以键/值对的形式存储到应用配置类的的变量和属性字典成员实例中,如果需要并把这些数据持久化存储到"appsettings.json"文件。
/// </remarks>
/// </summary>
public class AppSettingsHelper
{
#region 变量--私有/保护
/// <summary>
/// 【配置顺序】
/// <remarks>
/// 摘要:
/// 设置字典变量成员实例,该实例以键/值对的形式存储着所有继承了“IConfig”接口的具体实现类的实例。
/// </remarks>
/// </summary>
private static Dictionary<string, int> _configurationOrder;
#endregion
#region 方法
/// <param name="configurations">列表接口实例,该实例存储着所有继承于“IConfig”接口的具体实现类的实例</param>
/// <param name="fileProvider">自定义文件提供程序接口的1个指定实例。</param>
/// <param name="overwrite">指示是否为需要把“appsettings.json”文件中的内容,使用新的内容进行覆盖性替换,默认值:true,即进行覆盖性替换。</param>
/// <summary>
/// 【保存应用配置】
/// <remarks>
/// 摘要:
/// 把继承于“IConfig”接口的具体实现类的实例,以键/值对的形式存储到应用配置类的字典变量和属性成员实例中,如果需要并把这些数据持久化存储到"appsettings.json"文件。
/// </remarks>
/// <returns>
/// 返回:
/// 应用配置类的1个指定实例,该实例中的字典变量和属性成员实例中存储着所有继承了“IConfig”接口的具体实现类的实例。
/// </returns>
/// </summary>
public static AppSettings SaveAppSettings(IList<IConfig> configurations, INopFileProvider fileProvider, bool overwrite = true)
{
if (configurations is null)
throw new ArgumentNullException(nameof(configurations));
if (_configurationOrder is null)
_configurationOrder = configurations.ToDictionary(config => config.Name, config => config.GetOrder());
//把当前应用程序中所有继承了“IConfig”接口的具体实现类的实例,存储到单例实例中。
var appSettings = Singleton<AppSettings>.Instance ?? new AppSettings();
appSettings.Update(configurations);
Singleton<AppSettings>.Instance = appSettings;
//如果启动项中没有"appsettings.json"文件,则新建该文件。
// var filePath = fileProvider.MapPath(NopConfigurationDefaults.AppSettingsFilePath);
var filePath = fileProvider.MapPath("appsettings.json");
var fileExists = fileProvider.FileExists(filePath);
fileProvider.CreateFile(filePath);
//根据"appsettings.json"文件的JSON反序列化操作,对继承于“IConfig”接口的所有具体实现类进行实例化操作。
var configuration = JsonConvert.DeserializeObject<AppSettings>(fileProvider.ReadAllText(filePath, Encoding.UTF8))
?.Configuration
?? new();
//依次把所有继承了“IConfig”接口的具体实现类的实例,经过JSON编码格式的格式化过后的字符串存储到字典键/值对的值中。
foreach (var config in configurations)
{
configuration[config.Name] = JToken.FromObject(config);
}
//把所有承于“IConfig”接口的所有具体实现类的实例以顺序方式进行排序(例如:数据库配置类的实例就排第1)。
appSettings.Configuration = configuration
.SelectMany(outConfig => _configurationOrder.Where(inConfig => inConfig.Key == outConfig.Key).DefaultIfEmpty(),
(outConfig, inConfig) => new { OutConfig = outConfig, InConfig = inConfig })
.OrderBy(config => config.InConfig.Value)
.Select(config => config.OutConfig)
.ToDictionary(config => config.Key, config => config.Value);
//如果不存在"appsettings.json"文件或需要进行覆盖性替换,把所有承于“IConfig”接口的所有具体实现类的实例持久化存储到"appsettings.json"文件中。
if (!fileExists || overwrite)
{
var text = JsonConvert.SerializeObject(appSettings, Formatting.Indented);
fileProvider.WriteAllText(filePath, text, Encoding.UTF8);
}
return appSettings;
}
#endregion
}
}
5 Framework.Infrastructure.Extensions.ServiceCollectionExtensions
using Core;
using Core.Configuration;
using Core.Infrastructure;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Net;
namespace Framework.Infrastructure.Extensions
{
/// <summary>
/// 【服务集合扩展--类】
/// <remarks>
/// 摘要:
/// 通过当前类中的成员方法把一些具体实现类的实例,依赖注入到.Net(Core)内置依赖注入容器实例中。
/// </remarks>
/// </summary>
public static class ServiceCollectionExtensions
{
/// <param name="services">.Net(Core)框架内置依赖注入容器实例。</param>
/// <param name="builder">Web应用构建器的1个指定实例(Web应用构建器主要对基于.Net(Core)框架中的配置文件(*.json)进行读写操作,>=Net6)</param>
/// <summary>
/// 【配置应用设定】
/// <remarks>
/// 摘要:
/// 把当前程序中所有继承了“IConfig”接口的具体实现类的实例,依赖注入到.Net(Core)内置依赖注入容器实例中,如果需要并把这些数据持久化存储到"appsettings.json"文件。
/// </remarks>
/// </summary>
public static void ConfigureApplicationSettings(this IServiceCollection services, WebApplicationBuilder builder)
{
//有关于“ServicePointManager”见: https://learn.microsoft.com/zh-cn/dotnet/api/system.net.securityprotocoltype?view=net-7.0
ServicePointManager.SecurityProtocol = SecurityProtocolType.SystemDefault;
//获取自定义文件提供程序实例。
CommonHelper.DefaultFileProvider = new NopFileProvider(builder.Environment);
//获取Web应用程序域类型查找器实例。
var typeFinder = new WebAppTypeFinder();
//把Web应用程序域类型查找器实例存储到单例实例的字典成员实例中。
Singleton<ITypeFinder>.Instance = typeFinder;
//把Web应用程序域类型查找器实例,依赖注入到.Net(Core)内置依赖注入容器实例中。
services.AddSingleton<ITypeFinder>(typeFinder);
//通过反射方式把继承于“IConfig”的所有具体实现类进行实例化操作。
var configurations = typeFinder
.FindClassesOfType<IConfig>()
.Select(configType => (IConfig)Activator.CreateInstance(configType)!)
.ToList();
//依次对继承于“IConfig”的所有具体实现类的实例,根据“appsettings.json”文件中的相关数据进行设定。
foreach (var config in configurations)
builder.Configuration.GetSection(config.Name).Bind(config, options => options.BindNonPublicProperties = true);
//把当前程序中所有继承了“IConfig”接口的具体实现类的实例以键/值对的形式存储到应用配置类的的变量和属性字典成员实例中,如果需要并把这些数据持久化存储到"appsettings.json"文件。
var appSettings = AppSettingsHelper.SaveAppSettings(configurations, CommonHelper.DefaultFileProvider, false);
//把当前程序中所有继承了“IConfig”接口的具体实现类的实例,依赖注入到.Net(Core)内置依赖注入容器实例中。
services.AddSingleton(appSettings);
}
}
}
6 重构Program.cs文件中的数据库连接依赖注入
//把当前程序中所有继承了“IConfig”接口的具体实现类的实例,依赖注入到.Net(Core)内置依赖注入容器实例中,如果需要并把这些数据持久化存储到"appsettings.json"文件。
builder.Services.ConfigureApplicationSettings(builder);
builder.Services.AddScoped<INopFileProvider, NopFileProvider>();
//从单例实例的字典成员实例中获取数据库连接相关数据。
DataConfig _dataConfigSingleton = Singleton<AppSettings>.Instance.Configuration["ConnectionStrings"].ToObject<DataConfig>()!;
//从内置依赖注入容器中例中获取数据库连接相关数据。
//注意最好不要在内置依赖注入容器中调用“builder.Services.BuildServiceProvider()”,否则会出现“ASP0000”警告信息;这里只是用“builder.Services.BuildServiceProvider()”方法来对配置数据进行调试。
DataConfig _dataConfigServiceProvider = builder.Services.BuildServiceProvider().GetService<AppSettings>().Configuration["ConnectionStrings"].ToObject<DataConfig>()!;
//说明:如果想要“EntityFrameworkCore”中间件支持多数据库软件,则把选择条件中的所有中间件都注入到依赖注入到.Net(Core)框架内置容器即可,
//选择条件来限定当前程序只支持所设定的1个数据库软件,当然“DataConfig”类与“appsettings.json”文件也必须为支持多数据库软件进行重构。
if (_dataConfigServiceProvider.DataProvider.ToString().Equals("sqlserver", StringComparison.InvariantCultureIgnoreCase))
{
//实例化“EntityFrameworkCore”中间件只支持“SqlServer”数据库软件与当前程序进行CURD交互操作。
//把“Microsoft.EntityFrameworkCore.SqlServer”中间件实例,依赖注入到.Net(Core)框架内置容器中。
builder.Services.AddDbContext<EFCoreContext>(
//通过“DbContextOptionsBuilder”实例中的参数实例,为“Microsoft.EntityFrameworkCore.SqlServer”中间件的实例化提供参数实例,
//最终把“Microsoft.EntityFrameworkCore.SqlServer”中间件实例,依赖注入到.Net(Core)框架内置容器中。
//IIS发布部署连接字符串必须使用“SQL Server身份认证”数据库连接方式,才能实现发布部署程序与数据库的CURD的操作。
options => options.UseSqlServer(_dataConfigServiceProvider.ConnectionString));
}
else if (_dataConfigServiceProvider.DataProvider.ToString().Equals("mysql", StringComparison.InvariantCultureIgnoreCase))
{
//实例化“EntityFrameworkCore”中间件只支持“MySql”数据库软件与当前程序进行CURD交互操作。
//把“Microsoft.EntityFrameworkCore.SqlServer”中间件和“Pomelo.EntityFrameworkCore.MySql”实例,依赖注入到.Net(Core)框架内置容器中。
builder.Services.AddDbContext<EFCoreContext>(
//实现“Microsoft.EntityFrameworkCore”中间件实例与“MySql”数据库的连接。
options => options.UseMySql(_dataConfigServiceProvider.ConnectionString, MySqlServerVersion.LatestSupportedServerVersion));
}
按F5执行程序不管在 “Microsoft SQL Server”数据库软件中,还是在“MySql”数据库软件中都能自动生“ShopDemo” 数据库及其表。
对以上功能更为具体实现和注释见:230116_011shopDemo(配置类的反射方式实例化、单例和依赖注入)。