如何优雅的使用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