zoukankan      html  css  js  c++  java
  • 如何优雅的使用AbpSettings

    
    

    在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
  • 相关阅读:
    怎样从基本原理解释复制型共享账本的价值
    BZOJ3223:文艺平衡树——超详细题解
    BZOJ3223:文艺平衡树——超详细题解
    BZOJ3223:文艺平衡树——超详细题解
    BZOJ3223:文艺平衡树——超详细题解
    my stackoverflow
    my stackoverflow
    my stackoverflow
    my stackoverflow
    STM32 Flash 永久用户数据空间
  • 原文地址:https://www.cnblogs.com/crazyboy/p/12497279.html
Copyright © 2011-2022 走看看