­

如何優雅的使用AbpSettings

  • 2020 年 3 月 15 日
  • 筆記

在Abp中配置雖然使用方便,但是每個配置要先定義key,要去provider中定義,再最後使用key從ISetting中獲取還是挺麻煩的一件事,

最主要是獲取修改的時候,比如,修改用戶配置,是從獲取一批key/value來返回前端,並從前端提交修改保存就比較麻煩了。

很早之前做過一些嘗試,如下:

https://www.cnblogs.com/crazyboy/p/8064387.html

但是那個時候比較菜也沒怎麼搞太清楚所以感覺也不太好用。

之前也想過使用定義配置類使用基類中注入的ISettingManager的方式來處理,如下

        public string Item          {              get { return this.SettingManager.GetSettingValue(nameof(Item)); }              set { this.SettingManager.ChangeSettingForApplication(nameof(Item), value); }          }

但是這樣對配置類污染比較大,也就放棄了,前段時間在看Abp源程式碼的時候,突然想到,是否可以通過攔截器來代理配置類的get ,set方法來達到獲取和修改配置的目的呢

於是便開始了配置的改造工作,首先,定義一個配置的介面來註冊攔截器:

 1 using Abp.Dependency;   2   3 namespace SKYS.Charger.Configuration   4 {   5     /// <summary>   6     /// 配置類介面,要實現從SettingManager更新/獲取數據,請所有屬性用virtual標識   7     /// </summary>   8     public interface ISettings : ISingletonDependency   9     {  10  11     }  12 }

為了定義設置的一些配置,我們還需要定義一個特性用於設置的默認值/範圍等:

using Abp.Configuration;  using System;    namespace SKYS.Charger.Configuration  {      [AttributeUsage(AttributeTargets.Property)]      public class AutoSettingDefinitionAttribute : Attribute      {          public object DefaultValue { get; private set; }            public bool IsVisibleToClients { get; private set; }            public SettingScopes Scopes { get; private set; }            public AutoSettingDefinitionAttribute(object defaultValue, bool isVisibleToClients = true, SettingScopes scopes = SettingScopes.Application)          {              this.DefaultValue = defaultValue;              this.IsVisibleToClients = isVisibleToClients;              this.Scopes = scopes;          }      }  }

接下來,我們需要把所有繼承至ISettings的設置類都註冊到SettingProvider中,這裡我直接使用的反射,從屬性特性上讀取設置的配置:

 1 using Abp.Configuration;   2 using System.Collections.Generic;   3 using System.Linq;   4 using System.Reflection;   5   6 namespace SKYS.Charger.Configuration   7 {   8     /// <summary>   9     ///  10     /// </summary>  11     public class AutoSettingsProvider : SettingProvider  12     {  13         public override IEnumerable<SettingDefinition> GetSettingDefinitions(SettingDefinitionProviderContext context)  14         {  15             var settings = new List<SettingDefinition>();  16  17             var types = this.GetType().Assembly  18                                       .GetTypes()  19                                       .Where(t => t.IsClass && typeof(ISettings).IsAssignableFrom(t));  20  21             foreach (var type in types)  22             {  23                 var scopes = SettingScopes.All;  24                 foreach (var p in type.GetProperties())  25                 {  26                     var key = AutoSettingsUtils.CreateSettingName(type, p.Name);  27                     var isVisibleToClients = false;  28                     var defaultValue = AutoSettingsUtils.GetDefaultValue(p.PropertyType);  29                     var attr = p.GetCustomAttribute<AutoSettingDefinitionAttribute>();  30                     if (attr != null)  31                     {  32                         scopes = attr.Scopes;  33                         defaultValue = attr.DefaultValue;  34                         isVisibleToClients = attr.IsVisibleToClients;  35                     }  36                     settings.Add(new SettingDefinition(  37                            name: key,  38                            defaultValue: defaultValue?.ToString(),  39                            scopes: scopes,  40                            isVisibleToClients: isVisibleToClients  41                             ));  42                 }  43             }  44  45             return settings;  46         }  47     }  48 }

接下來定義一個Interceptor用於攔截設置類中屬性的get/set方法,在攔截器中注入了ISettingManager及AbpSession用於獲取和修改設置,在修改的時候如果scope支援User優先修改用戶設置,然後是租戶設置,最後是應用設置

 1 using Abp.Configuration;   2 using Abp.Runtime.Session;   3 using Castle.DynamicProxy;   4 using SKYS.Charger.Utilities;   5   6 namespace SKYS.Charger.Configuration   7 {   8     /// <summary>   9     /// 自動配置攔截器,用於獲取/修改配置值  10     /// </summary>  11     public class AutoSettingsInterceptor : IInterceptor  12     {  13         private readonly ISettingManager _settingManager;  14         private readonly ISettingDefinitionManager _settingDefinitionManager;  15         public IAbpSession AbpSession { get; set; }  16         public AutoSettingsInterceptor(ISettingManager settingManager, ISettingDefinitionManager settingDefinitionManager)  17         {  18             this._settingManager = settingManager;  19             this._settingDefinitionManager = settingDefinitionManager;  20             this.AbpSession = NullAbpSession.Instance;  21         }  22  23         protected void PostProceed(IInvocation invocation)  24         {  25             var setFlag = "set_";  26             var getFlag = "get_";  27  28             var isSet = invocation.Method.Name.StartsWith(setFlag);  29             var isGet = invocation.Method.Name.StartsWith(getFlag);  30             //非屬性方法不處理  31             if (!isSet && !isGet)  32                 return;  33  34             var pname = invocation.Method.Name.Replace(setFlag, "")  35                                               .Replace(getFlag, "");  36             var settingName = AutoSettingsUtils.CreateSettingName(invocation.TargetType, pname);  37             //配置 設置  38             if (isSet)  39             {  40                 var setting = this._settingDefinitionManager.GetSettingDefinition(settingName);  41                 this.ChangeSettingValue(setting, invocation.Arguments[0]?.ToString());  42             }  43             //配置 獲取  44             else  45             {  46                 var val = this._settingManager.GetSettingValue(settingName);  47                 invocation.ReturnValue = ConvertHelper.ChangeType(val, invocation.Method.ReturnType);  48             }  49         }  50         protected void ChangeSettingValue(SettingDefinition settings, object value)  51         {  52             var val = value?.ToString();  53             if (settings.Scopes.HasFlag(SettingScopes.User) && this.AbpSession.UserId.HasValue)  54                 this._settingManager.ChangeSettingForUser(this.AbpSession.ToUserIdentifier(), settings.Name, val);  55             else if (settings.Scopes.HasFlag(SettingScopes.Tenant) && this.AbpSession.TenantId.HasValue)  56                 this._settingManager.ChangeSettingForTenant(this.AbpSession.TenantId.Value, settings.Name, val);  57             else if (settings.Scopes.HasFlag(SettingScopes.Application))  58                 this._settingManager.ChangeSettingForApplication(settings.Name, val);  59         }  60  61         public void Intercept(IInvocation invocation)  62         {  63             invocation.Proceed();  64             this.PostProceed(invocation);  65         }  66     }  67 }

定義完以後,我們還需要註冊我們的攔截器,這裡我使用了一個Manager來註冊,通過傳入AbpModule中的Configuration來完成註冊

 1 using Abp.Configuration.Startup;   2 using Castle.Core;   3   4 namespace SKYS.Charger.Configuration   5 {   6     public class AutoSettingsManager   7     {   8         public static void Initialize(IAbpStartupConfiguration configuration)   9         {  10             configuration.IocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>  11             {  12                 if (typeof(ISettings).IsAssignableFrom(handler.ComponentModel.Implementation))  13                 {  14                     handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AutoSettingsInterceptor)));  15                 }  16             };  17  18        //把自動屬性的Provider註冊  19             configuration.Settings.Providers.Add<AutoSettingsProvider>();  20         }  21     }  22 }

 

 然後在你定義配置類型的Module的PreInitialize()中完成註冊:

//自動配置初始化              AutoSettingsManager.Initialize(Configuration);

 

 到這裡我們的工作基本上也就完成了,接下來我們就可以定義我們自己的設置類了,因為我們注入使用是類,所以定義的屬性都需要加上virtual以便攔截器能正常工具 

 1 using Abp.AutoMapper;   2 using Abp.Configuration;   3   4 namespace SKYS.Charger.Configuration.Settings   5 {   6     [AutoMap(typeof(AppSettings))]   7     public class AppSettings : ISettings   8     {   9         [AutoSettingDefinition("SKYS.Charger")]  10         public virtual string SystemName { get; set; }  11  12         [AutoSettingDefinition(20)]  13         public virtual int PageSize { get; set; }  14  15         /// <summary>  16         /// 得現手續費  17         /// </summary>  18         [AutoSettingDefinition(0.02)]  19         public virtual decimal TakeServiceFeeRate { get; set; }  20     }  21 }

在任意使用的地方,直接注入即可使用,並且只要是注入的配置類型,設置它的屬性即可完成修改並保存到資料庫,獲取也是直接從ISettingManager中獲取值,再配合前端修改的時候就方便多了

 1 namespace SKYS.Charger.Configuration   2 {   3     public class ConfigurationAppService : ApplicationService   4     {   5         private readonly AppSettings _appSettings;   6         public ConfigurationAppService(AppSettings appSettings)   7         {   8             this._appSettings = appSettings;   9         }  10  11         /// <summary>  12         /// 獲取系統配置  13         /// </summary>  14         public async Task<AppSettings> GetSystemSettings()  15         {  16             return await Task.FromResult(_appSettings);  17         }  18         /// <summary>  19         /// 修改系統配置  20         /// </summary>  21         [ManagerAuthorize]  22         public async Task ChangeSystemSettings(AppSettings appSettings)  23         {  24             this.ObjectMapper.Map(appSettings, _appSettings);  25  26             await Task.CompletedTask;  27         }  28     }  29 }

 是不是比原來的使用方式簡單了很多呢,因為是所有配置類型都是ISingletonDependency在不方便的地方還可以直接使用IocManager.Instance.Resolve<AppSettings>()直接獲取:

 1     public class PagedRequestFilter : IShouldNormalize   2     {   3         //public ISettingManager SettingManager { get; set; }   4   5         public const int DefaultSize = 20;   6   7         //[Range(1, 10000)]   8         public int Page { get; set; }   9  10         //[Range(1,100)]  11         public int Size { get; set; }  12  13         public void Normalize()  14         {  15             if (this.Page <= 0)  16                 this.Page = 1;  17             if (this.Size <= 0)  18             {  19                 var appSettings = IocManager.Instance.Resolve<AppSettings>();  20                 this.Size = appSettings.PageSize;  21             }  22         }  23     }

 最後附上中間使用過的兩個工具類AutoSettingsUtils和ConvertHelper

    public static class AutoSettingsUtils      {          public static string CreateSettingName(Type type, string propertyName)          {              return $"{type.Name}.{propertyName}";          }            public static object GetDefaultValue(Type targetType)          {              return targetType.IsValueType ? Activator.CreateInstance(targetType) : null;          }      }

View Code

 1     /// <summary>   2     /// 數據轉換幫助類   3     /// </summary>   4     public static class ConvertHelper   5     {   6         #region = ChangeType =   7         public static object ChangeType(object obj, Type conversionType)   8         {   9             return ChangeType(obj, conversionType, Thread.CurrentThread.CurrentCulture);  10         }  11         public static object ChangeType(object obj, Type conversionType, IFormatProvider provider)  12         {  13             #region Nullable  14             Type nullableType = Nullable.GetUnderlyingType(conversionType);  15             if (nullableType != null)  16             {  17                 if (obj == null)  18                 {  19                     return null;  20                 }  21                 return Convert.ChangeType(obj, nullableType, provider);  22             }  23             #endregion  24             if (typeof(System.Enum).IsAssignableFrom(conversionType))  25             {  26                 return Enum.Parse(conversionType, obj.ToString());  27             }  28             return Convert.ChangeType(obj, conversionType, provider);  29         }  30         #endregion  31     }

View Code