• 跟我学: 使用 fireasy 搭建 asp.net core 项目系列之三 —— 配置


    ==== 目录 ====

      跟我学: 使用 fireasy 搭建 asp.net core 项目系列之一 —— 开篇

      跟我学: 使用 fireasy 搭建 asp.net core 项目系列之二 —— 准备

      跟我学: 使用 fireasy 搭建 asp.net core 项目系列之三 —— 配置

        其实从 mvc5 迁移到 core,项目的差异化主要就体现在配置上。在 core 的世界里,万物都依赖于 ioc,因此,对于初学 core 的人来说,首先要搞懂的一个知识点就是 ioc。

        fireasy 支持 core 项目,因此在配置上也有一些特殊的地方。

        一、appsettings.json

        appsettings.json 是 core 项目的标准配置文件,你当然可以使用其他的文件名来存储,但应注意要在 Program.cs 中手动指定文件路径。

            public static IWebHost BuildWebHost(string[] args)
            {
                var config = new ConfigurationBuilder()
                    .SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json", optional: true)
                    .AddJsonFile("hosting.json", optional: true)
                    .AddCommandLine(args)
                    .Build();
    
                return WebHost.CreateDefaultBuilder(args)
                    .UseConfiguration(config)
                    .UseStartup<Startup>()
                    .Build();
            }
    

        fireasy 将日志、缓存、订阅发布、数据库连接、ioc等全放在 appsettings.json 里,以下是一个完整的配置实例:

    {
      "fireasy": {
        "dataGlobal": { //数据层的全局设置
          "options": {
            "attachQuote": true //是否在sql语句中自动附加逃逸符,即[]、``等
          }
        },
        "dataInstances": { //数据库连接实例
          "default": "sqlite", //默认使用的实例,如果没有指定,则使用 settings 中的第一项
          "settings": {
            "sqlite": {
              "providerType": "SQLite",
              "connectionString": "Data source=|datadirectory|../../../../database/zero.db3;version=3;tracking=true"
            },
            "mysql": {
              "providerType": "MySql",
              "connectionString": "Data Source=localhost;database=zero;User Id=root;password=faib;pooling=true;charset=utf8;Treat Tiny As Boolean=false;tracking=true"
            },
            "sqlserver": {
              "providerType": "MsSql",
              "connectionString": "data source=.;user id=sa;password=123;initial catalog=zero;tracking=true"
            },
            "oracle": {
              "providerType": "Oracle",
              "connectionString": "Data Source=orcl;User ID=ZERO;Password=123;tracking=true"
            }
          }
        },
        "dataConverters": { //数据转换器
          "settings": [
            {
              "sourceType": "Fireasy.Data.CodedData, Fireasy.Data",
              "converterType": "Fireasy.Zero.Infrastructure.CodedDataConverter, Fireasy.Zero.Infrastructure"
            }
          ]
        },
        "loggings": { //日志组件
          "settings": {
            "db": {
              "type": "Fireasy.Zero.Services.Impls.LogService, Fireasy.Zero.Services"
            }
          }
        },
        "cachings": { //缓存组件
          "settings": {
            "redis": {
              "type": "Fireasy.Redis.CacheManager, Fireasy.Redis",
              "config": {
                "defaultDb": 1,
                "password": "test",
                "host": [
                  {
                    "server": "localhost"
                  }
                ]
              }
            }
          }
        },
        "subscribers": { //订阅发布
          "default": "rabbit", //默认使用的实例
          "settings": {
            "redis": { //使用redis
              "type": "Fireasy.Redis.RedisSubscribeManager, Fireasy.Redis",
              "config": {
                "host": [
                  {
                    "server": "localhost"
                  }
                ]
              }
            },
            "rabbit": { //使用rabbit
              "type": "Fireasy.RabbitMQ.SubscribeManager, Fireasy.RabbitMQ",
              "config": {
                "userName": "test",
                "password": "test",
                "server": "amqp://localhost:5672"
              }
            }
          }
        },
        "containers": { //ioc配置
          "settings": {
            "default": [
              {
                "assembly": "Fireasy.Zero.Services" //整个程序集导入
              },
              {
                "serviceType": "Fireasy.Zero.Infrastructure.IFileStorageProvider, Fireasy.Zero.Infrastructure",
                "implementationType": "Fireasy.Zero.Infrastructure.FileServerStorageProvider, Fireasy.Zero.Infrastructure"
              }
            ]
          }
        }
      }
    }

        二、基本配置

        定位到 Fireasy.Zero.Web 项目的 Startup.cs 文件,找到 ConfigureServices 方法,将以下代码加入到方法里面:

            // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddFireasy(Configuration)
                    .AddIoc(ContainerUnity.GetContainer()); //添加 appsettings.json 里的 ioc 配置
    
                services.AddMvc()
                    .ConfigureFireasyMvc() // fireasy.web.mvc 相关的配置
                    .ConfigureEasyUI();  //easyui 相关的配置
            }
    

        扩展方法 AddFireasy 为的是将 appsettings.json 中的相关配置加载到到环境中。这里它的原理可以多给大家说一下,以便了解它是如何工作的。查看 AddFireasy 方法,源码如下:

            public static IServiceCollection AddFireasy(this IServiceCollection services, IConfiguration configuration, Action<Fireasy.Common.CoreOptions> setupAction = null)
            {
                ConfigurationUnity.Bind(Assembly.GetCallingAssembly(), configuration, services);
    
                var options = new Fireasy.Common.CoreOptions();
                setupAction?.Invoke(options);
    
                return services;
            }
    

        查看 ConfigurationUnity.Bind 方法:

            public static void Bind(Assembly callAssembly, IConfiguration configuration, IServiceCollection services = null)
            {
                var assemblies = new List<Assembly>();
    
                FindReferenceAssemblies(callAssembly, assemblies);
    
                foreach (var assembly in assemblies)
                {
                    var type = assembly.GetType("Microsoft.Extensions.DependencyInjection.ConfigurationBinder");
                    if (type != null)
                    {
                        var method = type.GetMethod("Bind", BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(IServiceCollection), typeof(IConfiguration) }, null);
                        if (method != null)
                        {
                            method.Invoke(null, new object[] { services, configuration });
                        }
                    }
                }
    
                assemblies.Clear();
            }
    

        它实际上是遍列当前程序集所引用的所有程序集,查看每个程序集下的特定类 Microsoft.Extensions.DependencyInjection.ConfigurationBinder,然后进行反射调用 Bind 方法。因此,每一个 fireasy 的类库都会有这样一个类,来接收 AddFireasy 的统一配置。

        比如 Fireasy.Common 下的这个类的内容为:

    internal class ConfigurationBinder
    {
        internal static void Bind(IServiceCollection services, IConfiguration configuration)
        {
            ConfigurationUnity.Bind<LoggingConfigurationSection>(configuration);
            ConfigurationUnity.Bind<CachingConfigurationSection>(configuration);
            ConfigurationUnity.Bind<ContainerConfigurationSection>(configuration);
            ConfigurationUnity.Bind<SubscribeConfigurationSection>(configuration);
            ConfigurationUnity.Bind<ImportConfigurationSection>(configuration);
    
            if (services != null)
            {
                services.AddLogger().AddCaching().AddSubscriber();
            }
        }
    }
    

        比如 Fireasy.Data 下的这个类的内容为:

    internal class ConfigurationBinder
    {
        internal static void Bind(IServiceCollection services, IConfiguration configuration)
        {
            ConfigurationUnity.Bind<GlobalConfigurationSection>(configuration);
            ConfigurationUnity.Bind<ProviderConfigurationSection>(configuration);
            ConfigurationUnity.Bind<ConverterConfigurationSection>(configuration);
            ConfigurationUnity.Bind<InstanceConfigurationSection>(configuration);
        }
    }
    

        可见它们实际上将 IConfiguration 对象进行配置,将日志、缓存、ioc容器、订阅发布等从配置中读出,放到内存当中。这样,在项目中的任何地方,都可以使用以下的方法来获取相对应的对象:

            private class TestClass
            {
                void Test()
                {
                    //获取日志的配置
                    var logCfg = ConfigurationUnity.GetSection<Fireasy.Common.Logging.Configuration.LoggingConfigurationSection>();
    
                    //获取默认日志记录对象
                    var log = Fireasy.Common.Logging.LoggerFactory.CreateLogger();
    
                    //获取缓存的配置
                    var cacheCfg = ConfigurationUnity.GetSection<Fireasy.Common.Caching.Configuration.CachingConfigurationSection>();
    
                    //获取默认缓存管理对象
                    var cache = Fireasy.Common.Caching.CacheManagerFactory.CreateManager();
                }
            }

        扩展方法 AddIoc 是将 fireasy 中的 ioc 容器中的相关抽象与实现映射添加到 core 本身的 ioc 集合中,使两者融合为一体,在 fireasy 中,ioc 是由 ContainerUnity 来管理的,它可以配置多个容器。源码如下:

            public static IServiceCollection AddIoc(this IServiceCollection services, Container container = null)
            {
                container = container ?? ContainerUnity.GetContainer();
                foreach (AbstractRegistration reg in container.GetRegistrations())
                {
                    if (reg is SingletonRegistration singReg)
                    {
                        services.AddSingleton(singReg.ServiceType, CheckAopProxyType(singReg.ImplementationType));
                    }
                    else if (reg.GetType().IsGenericType && reg.GetType().GetGenericTypeDefinition() == typeof(FuncRegistration<>))
                    {
                        services.AddTransient(reg.ServiceType, s => reg.Resolve());
                    }
                    else
                    {
                        services.AddTransient(reg.ServiceType, CheckAopProxyType(reg.ImplementationType));
                    }
                }
    
                return services;
            }
    

        二、mvc 配置

        扩展方法 ConfigureFireasyMvc 中本 mvc 的一些配置。

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddMvc()
                    .ConfigureFireasyMvc(options =>
                        {
                            options.DisableModelValidator = true;
                            options.UseErrorHandleFilter = true;
                            options.UseJsonModelBinder = true;
                            options.UseTypicalJsonSerializer = true;
                            options.JsonSerializeOption.IgnoreNull = true;
                            options.JsonSerializeOption.Converters.Add(new Fireasy.Data.Entity.LightEntityJsonConverter());
                            options.JsonSerializeOption.Converters.Add(new Common.Serialization.FullDateTimeJsonConverter());
                        });
            }
    

        可以设置 MvcOptions 参数对象中的某些属性来达到不同的效果:

        DisableModelValidator 覆盖本身 mvc 自带的 IObjectModelValidator 对象,使它在调用 action 时不对 model 进行验证。因为在此示例中,我们使用 easyui 前端框架,在 ui 上就有数据的验证,并且在 Entity 层还有一次验证,因此将其关闭。

        UseJsonModelBinder 是使用 fireasy 特有的 model 绑定方式,即使用 json 充序列化的方式传递复杂的对象及集合,众所周知,在 mvc 里要传递一个对象,或一个集合,只能使用 name=hxd&sex=1&birthday=2019-1-1 这种方式,因此对于复杂的对象来说,就先麻烦了。使用此开关后,只需要传递 info={ name: "hxd", sex: 1, birthday: "2019-1-1" } 就行了。

        UseErrorHandleFilter 使用自定义的异常处理过滤器。在 HandleErrorAttribute 这个类中,当异常类型是 ClientNotificationException 时,将直接返回其 Message,否则记录日志,并返回友好的错误提示信息。因此,在业务层,可以多使用 ClientNotificationException  来通知前端具体的异常信息。

        UseTypicalJsonSerializer 使用 fireasy 的 json 序列化方法,它将抛弃 Newtonsoft。原因是,Entity 返回时不再做 ViewModel 的映射处理,那么不可避免地,在 Entity 对象中会包含一些延迟加载的属性,在使用 Newtonsoft 时将发生不可原谅的循环引用异常,造成程序崩溃。fireasy 中引入了一个 ILazyManager 接口,Entity 受此管理后,那些未加载出来的属性,则不会被序列化。另外一种解决办法是,引入 Fireasy.Fireasy.Newtonsoft,将 LazyObjectJsonConverter 添加到 Converters 中去。

        services.AddMvc()
            .AddJsonOptions(options =>
                {
                    options.SerializerSettings.Converters.Add(new Fireasy.Newtonsoft.LazyObjectJsonConverter());
                    options.SerializerSettings.ContractResolver = new DefaultContractResolver();
                });
    

      JsonSerializeOption 即 fireasy json 序列化的一些全局配置,尤其要注意的是,这里在 Converters 里添加了一个 LightEntityJsonConverter ,它的目的是在 action model 绑定时,通过它来进行反序列化,这是为什么呢,后面的章节中会提到。

        扩展方法 ConfigureEasyUI 主要是用来配置 easyui 的一些数据验证规则,它默认绑定了ValidateBoxSettingBinder 和 NumberBoxSettingBinder 两种规则,这里就不再介绍了。

        三、数据库配置

        数据库配置是核心,所以着重说一下。参见 appsettings.json 文件中的 fireasy:dataInstances 节点,它的配置其实很易懂,无非就是指定 providerType 和 connectionString。

        providerType 是数据库的提供者,对应不同的数据库,这里可以取 MsSql、MySQL、Oracle、SQLite、Firebird、PostgreSql、以及 OleDb。

        如果这些都还不能满足你,你可以自行去实现 provider ,然后通过 providerName 来进行指定。这个暂时先不说了,后面有一个 Mongodb 的章节介绍。

        不同的 provider 需要从 nuget 里引用相对应的程序集,从上至下优先,可对照下表:

    providerType .net core .net framework
    MsSql 不需要 不需要
    MySQL MySql.Data
    MySqlConnector
    同 .net core
    SQLIte System.Data.SQLite
    Microsoft.Data.Sqlite
    Spreads.SQLite
    System.Data.SQLite
    Oracle Oracle.ManagedDataAccess
    Mono.Data.OracleClientCore
    Oracle.ManagedDataAccess
    Oracle.DataAccess
    System.Data.OracleClient
    Firebird FirebirdSql.Data.FirebirdClient 同 .net core
    PostgreSql Npgsql 同 .net core
    OleDb 不需要 不需要

        四、DbContext 配置

        DbContext 与 上节的数据库配置息息相关。DbContext 是继承自 EntityContext 的,EntityContext 有两个构造函数。

        public class DbContext : EntityContext
        {
            /// <summary>
            /// 自定义 EntityContextOptions 参数方式
            /// </summary>
            /// <param name="options"></param>
            public DbContext(EntityContextOptions options)
                : base (options)
            {
            }
    
            /// <summary>
            /// 使用数据库配置实例名方式
            /// </summary>
            /// <param name="name"></param>
            public DbContext(string name)
                : base (name)
            {
            } 
    }
    

        一般是使用第二种方式,name 即数据库配置中的实例名,如果不指定,则由 default 来决定,从 appsettings.json 可得知,默认是使用 sqlite 数据库,如果这里使用了 mysql 则会使用 MySQL 数据库。

        第一种方式则用在需要在程序中动态指定 provider 和 connection string 的时候使用,它主要通过 ContextFactory 这个委托来指定。下面就是一个很好的例子。

        public class TestClass
        {
            void Test()
            {
                var providerName = "SQLite";
                var connectionStr = "Data source=|datadirectory|../../../../database/zero.db3;version=3;tracking=true";
    
                using (var db = new DbContext(new EntityContextOptions
                    {
                        ContextFactory = () => new EntityContextInitializeContext(Data.Provider.ProviderHelper.GetDefinedProviderInstance(providerName), connectionStr)
                    }))
                {
    
                }
            }
        }
    

        原来业务层中使用 DbContext 是在每个方法里 using (var db = new DbContext()) 来使用的,当时是对于 ioc 对象的释放机制不是太了解。经过测试后,将 DbContext 通过构造器注入的方式注入也是完全没有问题的。修改一下 Startup.cs  中的 ConfigureServices 方法,与 Entity Framework 类似的,使用 AddEntityContext 方法(Entity Framework 中是 AddDbContext 方法)。

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddEntityContext<DbContext>(options =>
                        {
                            options.AutoCreateTables = true; //此项为 true 时, 采用 codefirst 模式维护数据库表
                            options.NotifyEvents = true; //此项设为 true 时, 上面的实体持久化订阅通知才会触发
                        });
            }
    

     这里的 EntityContextOptions 参数有以下几个设置项:

        AutoCreateTables 使用类似于 CodeFirlst 的方式,检查实体映射的数据表是否存在,没有的话则创建,同时对于已经存在的数据表,会对属性进行比对,增加新的字段,删除的字段不进行处理。

        NotifyEvents 是否触发持久化事件,比如实体的创建之前、创建之后、修改之前、修改之后等等,都会以事件消息的方式通过消息订阅进行发布,定义一个消费者来接收进行处理。

        RecompileAssembly 是否重新编译实体程序集。由于 fireasy 中的实体类的属性使用了 virtual 修饰,此开关打开时,将使用 aop 技术对实体类进行动态编译,使之在属性被修改时能够记录下来,达到按需更新的效果。

        ValidateEntity 是否在持久化之前进行实体的验证,如果前端把控严格的话,可以将此开关关闭,免得影响性能。

        上面的 AddEntityContext 还存在一个问题,即 DbContext 的引用,你也可以将 DbContext 放到 appsettings.json 的 ioc 配置节中,这样 core 项目就不必要引用 DbContext 的项目了。如下配置后,可以直接使用 services.AddEntityContext() 方法。

    {
      "fireasy": {
        "containers": { //ioc配置
          "settings": {
            "default": [
              {
                "serviceType": "Fireasy.Zero.Services.Impls.DbContext, Fireasy.Zero.Services"
              }
            }
          }
        }
      }
    }
    

      

        好了,配置这块还是算比较复杂的了,但是通过这样的配置,项目的灵活度却是提高了不少。写这篇的目的,其实更多的目的是给大家提供一种思路,使大家对 .net core 有一个更深一步的了解。

      

    ==================================相关资源==================================

    fireasy源码:  https://github.com/faib920/fireasy2

    zero源码:  https://github.com/faib920/zero

    代码生成器:  http://www.fireasy.cn/soft/codebuilder/CodeBuilder2setup.exe

    作者:黄旭东
    出处:http://fireasy.cnblogs.com
    版权声明:本文的版权归作者与博客园共有。转载时须注明本文的详细链接,否则作者将保留追究其法律责任。
  • 相关阅读:
    对于函数中多个返回值的处理
    Docker-compose 安裝单机版redis
    设计模式七大设计原则
    UML 设计技巧
    使用Docker 容器配置nexus3.29 私有仓库
    分布式消息Kafka通信原理分析
    分布式消息Kafka通信
    使用docker 搭建nexus3.29
    分布式消息Kafka初步认识及基本应用
    Dubbo 常用配置及源码分析
  • 原文地址:https://www.cnblogs.com/fireasy/p/10899072.html
Copyright © 2020-2023  润新知