How to use AbpSettings gracefully

Keywords: ASP.NET Attribute Session Database

Although it's convenient to use the configuration in Abp, it's a troublesome thing to define the key in each configuration first, define it in the provider, and then use the key to get it from ISetting,

The most important thing is to get changes. For example, to modify user configuration, it is more difficult to get a batch of key/value to return to the front end, and submit changes and save from the front end.

Some attempts have been made a long time ago, as follows:

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

But at that time, I didn't know how to compare the dishes, so I didn't think it was easy to use.

I also thought about using the ISettingManager injected into the base class to define the configuration class, as follows

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

But the pollution to configuration class is so great that I gave up. When I was looking at the Abp source code some time ago, I suddenly thought, can I use interceptors to proxy the get and set methods of configuration class to get and modify the configuration

So we started the configuration transformation. First, we defined a configured interface to register interceptors:

 1 using Abp.Dependency;
 2 
 3 namespace SKYS.Charger.Configuration
 4 {
 5     /// <summary>
 6     /// To configure a class interface, you need to implement the SettingManager To update/Get data, please use virtual Identification
 7     /// </summary>
 8     public interface ISettings : ISingletonDependency
 9     {
10 
11     }
12 }

In order to define some configurations of settings, we also need to define a property for setting default values / ranges, etc

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;
        }
    }
}

Next, we need to register all the settings classes inherited from ISettings into SettingProvider. Here, I directly use the reflection to read the configuration of settings from the property properties:

 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 }

Next, define a get/set method used by Interceptor to intercept the properties in the setting class. ISettingManager and AbpSession are injected into Interceptor to obtain and modify the settings. If scope supports User priority to modify User settings, then tenant settings, and finally application settings

 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     /// Automatic configuration interceptor for obtaining/Modify configuration values
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             //Non attribute methods do not process
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             //Configure settings
38             if (isSet)
39             {
40                 var setting = this._settingDefinitionManager.GetSettingDefinition(settingName);
41                 this.ChangeSettingValue(setting, invocation.Arguments[0]?.ToString());
42             }
43             //Configuration acquisition
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 }

After the definition, we also need to register our interceptor. Here I use a Manager to register, and complete the registration by passing in the Configuration in the AbpModule

 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        //Register the Provider of automatic property
19             configuration.Settings.Providers.Add<AutoSettingsProvider>();
20         }
21     }
22 }

 

Then complete the registration in PreInitialize() of the Module where you define the configuration type:

//Automatic configuration initialization
            AutoSettingsManager.Initialize(Configuration);

 

Our work is basically finished here. Next, we can define our own setting class. Because we inject and use the class, we need to add virtual to all the defined properties so that the interceptor can use normal tools

 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         /// Commission for cash
17         /// </summary>
18         [AutoSettingDefinition(0.02)]
19         public virtual decimal TakeServiceFeeRate { get; set; }
20     }
21 }

In any place of use, direct injection can be used, and as long as the configuration type of injection is set, its properties can be modified and saved to the database, and obtaining values directly from ISettingManager, and then it is much more convenient to cooperate with the front-end modification

 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         /// Get system configuration
13         /// </summary>
14         public async Task<AppSettings> GetSystemSettings()
15         {
16             return await Task.FromResult(_appSettings);
17         }
18         /// <summary>
19         /// Modify system configuration
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 }

Is it much simpler than the original way of use? Because all configuration types are ISingletonDependency, which can be obtained directly by using iocmanager. Instance. Resolve < Appsettings > () in inconvenient places:

 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     }

Finally, two tool classes used in the middle, AutoSettingsUtils and ConvertHelper, are attached

    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;
        }
    }
 1     /// <summary>
 2     /// Data conversion help class
 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     }

Posted by klaibert26 on Sun, 15 Mar 2020 01:51:36 -0700