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

==== 目錄 ====html

  跟我學: 使用 fireasy 搭建 asp.net core 項目系列之一 —— 開篇前端

  跟我學: 使用 fireasy 搭建 asp.net core 項目系列之二 —— 準備mysql

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

 

    其實從 mvc5 遷移到 core,項目的差別化主要就體如今配置上。在 core 的世界裏,萬物都依賴於 ioc,所以,對於初學 core 的人來講,首先要搞懂的一個知識點就是 ioc。github

    fireasy 支持 core 項目,所以在配置上也有一些特殊的地方。web

 

    1、appsettings.jsonredis

    appsettings.json 是 core 項目的標準配置文件,你固然可使用其餘的文件名來存儲,但應注意要在 Program.cs 中手動指定文件路徑。sql

        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"
          }
        ]
      }
    }
  }
}

 

    2、基本配置json

    定位到 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;
        }

 

    2、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 兩種規則,這裏就再也不介紹了。

 

    3、數據庫配置

    數據庫配置是核心,因此着重說一下。參見 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 不須要 不須要

 

 

    4、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

相關文章
相關標籤/搜索