第17章 配置类的反射方式实例化、单例和依赖注入

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

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”文件中获取1JSON/值对中的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(配置类的反射方式实例化、单例和依赖注入)。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6