如何优雅的使用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方法来达到获取和修改配置的目的呢
于是便开始了配置的改造工作,首先,定义一个配置的接口来注册拦截器:
using Abp.Dependency; namespace SKYS.Charger.Configuration
{
/// <summary>
/// 配置类接口,要实现从SettingManager更新/获取数据,请所有属性用virtual标识
/// </summary>
public interface ISettings : ISingletonDependency
{ }
}
为了定义设置的一些配置,我们还需要定义一个特性用于设置的默认值/范围等:
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中,这里我直接使用的反射,从属性特性上读取设置的配置:
using Abp.Configuration;
using System.Collections.Generic;
using System.Linq;
using System.Reflection; namespace SKYS.Charger.Configuration
{
/// <summary>
///
/// </summary>
public class AutoSettingsProvider : SettingProvider
{
public override IEnumerable<SettingDefinition> GetSettingDefinitions(SettingDefinitionProviderContext context)
{
var settings = new List<SettingDefinition>(); var types = this.GetType().Assembly
.GetTypes()
.Where(t => t.IsClass && typeof(ISettings).IsAssignableFrom(t)); foreach (var type in types)
{
var scopes = SettingScopes.All;
foreach (var p in type.GetProperties())
{
var key = AutoSettingsUtils.CreateSettingName(type, p.Name);
var isVisibleToClients = false;
var defaultValue = AutoSettingsUtils.GetDefaultValue(p.PropertyType);
var attr = p.GetCustomAttribute<AutoSettingDefinitionAttribute>();
if (attr != null)
{
scopes = attr.Scopes;
defaultValue = attr.DefaultValue;
isVisibleToClients = attr.IsVisibleToClients;
}
settings.Add(new SettingDefinition(
name: key,
defaultValue: defaultValue?.ToString(),
scopes: scopes,
isVisibleToClients: isVisibleToClients
));
}
} return settings;
}
}
}
接下来定义一个Interceptor用于拦截设置类中属性的get/set方法,在拦截器中注入了ISettingManager及AbpSession用于获取和修改设置,在修改的时候如果scope支持User优先修改用户设置,然后是租户设置,最后是应用设置
using Abp.Configuration;
using Abp.Runtime.Session;
using Castle.DynamicProxy;
using SKYS.Charger.Utilities; namespace SKYS.Charger.Configuration
{
/// <summary>
/// 自动配置拦截器,用于获取/修改配置值
/// </summary>
public class AutoSettingsInterceptor : IInterceptor
{
private readonly ISettingManager _settingManager;
private readonly ISettingDefinitionManager _settingDefinitionManager;
public IAbpSession AbpSession { get; set; }
public AutoSettingsInterceptor(ISettingManager settingManager, ISettingDefinitionManager settingDefinitionManager)
{
this._settingManager = settingManager;
this._settingDefinitionManager = settingDefinitionManager;
this.AbpSession = NullAbpSession.Instance;
} protected void PostProceed(IInvocation invocation)
{
var setFlag = "set_";
var getFlag = "get_"; var isSet = invocation.Method.Name.StartsWith(setFlag);
var isGet = invocation.Method.Name.StartsWith(getFlag);
//非属性方法不处理
if (!isSet && !isGet)
return; var pname = invocation.Method.Name.Replace(setFlag, "")
.Replace(getFlag, "");
var settingName = AutoSettingsUtils.CreateSettingName(invocation.TargetType, pname);
//配置 设置
if (isSet)
{
var setting = this._settingDefinitionManager.GetSettingDefinition(settingName);
this.ChangeSettingValue(setting, invocation.Arguments[]?.ToString());
}
//配置 获取
else
{
var val = this._settingManager.GetSettingValue(settingName);
invocation.ReturnValue = ConvertHelper.ChangeType(val, invocation.Method.ReturnType);
}
}
protected void ChangeSettingValue(SettingDefinition settings, object value)
{
var val = value?.ToString();
if (settings.Scopes.HasFlag(SettingScopes.User) && this.AbpSession.UserId.HasValue)
this._settingManager.ChangeSettingForUser(this.AbpSession.ToUserIdentifier(), settings.Name, val);
else if (settings.Scopes.HasFlag(SettingScopes.Tenant) && this.AbpSession.TenantId.HasValue)
this._settingManager.ChangeSettingForTenant(this.AbpSession.TenantId.Value, settings.Name, val);
else if (settings.Scopes.HasFlag(SettingScopes.Application))
this._settingManager.ChangeSettingForApplication(settings.Name, val);
} public void Intercept(IInvocation invocation)
{
invocation.Proceed();
this.PostProceed(invocation);
}
}
}
定义完以后,我们还需要注册我们的拦截器,这里我使用了一个Manager来注册,通过传入AbpModule中的Configuration来完成注册
using Abp.Configuration.Startup;
using Castle.Core; namespace SKYS.Charger.Configuration
{
public class AutoSettingsManager
{
public static void Initialize(IAbpStartupConfiguration configuration)
{
configuration.IocManager.IocContainer.Kernel.ComponentRegistered += (key, handler) =>
{
if (typeof(ISettings).IsAssignableFrom(handler.ComponentModel.Implementation))
{
handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(AutoSettingsInterceptor)));
}
}; //把自动属性的Provider注册
configuration.Settings.Providers.Add<AutoSettingsProvider>();
}
}
}
然后在你定义配置类型的Module的PreInitialize()中完成注册:
//自动配置初始化
AutoSettingsManager.Initialize(Configuration);
到这里我们的工作基本上也就完成了,接下来我们就可以定义我们自己的设置类了,因为我们注入使用是类,所以定义的属性都需要加上virtual以便拦截器能正常工具
using Abp.AutoMapper;
using Abp.Configuration; namespace SKYS.Charger.Configuration.Settings
{
[AutoMap(typeof(AppSettings))]
public class AppSettings : ISettings
{
[AutoSettingDefinition("SKYS.Charger")]
public virtual string SystemName { get; set; } [AutoSettingDefinition()]
public virtual int PageSize { get; set; } /// <summary>
/// 得现手续费
/// </summary>
[AutoSettingDefinition(0.02)]
public virtual decimal TakeServiceFeeRate { get; set; }
}
}
在任意使用的地方,直接注入即可使用,并且只要是注入的配置类型,设置它的属性即可完成修改并保存到数据库,获取也是直接从ISettingManager中获取值,再配合前端修改的时候就方便多了
namespace SKYS.Charger.Configuration
{
public class ConfigurationAppService : ApplicationService
{
private readonly AppSettings _appSettings;
public ConfigurationAppService(AppSettings appSettings)
{
this._appSettings = appSettings;
} /// <summary>
/// 获取系统配置
/// </summary>
public async Task<AppSettings> GetSystemSettings()
{
return await Task.FromResult(_appSettings);
}
/// <summary>
/// 修改系统配置
/// </summary>
[ManagerAuthorize]
public async Task ChangeSystemSettings(AppSettings appSettings)
{
this.ObjectMapper.Map(appSettings, _appSettings); await Task.CompletedTask;
}
}
}
是不是比原来的使用方式简单了很多呢,因为是所有配置类型都是ISingletonDependency在不方便的地方还可以直接使用IocManager.Instance.Resolve<AppSettings>()直接获取:
public class PagedRequestFilter : IShouldNormalize
{
//public ISettingManager SettingManager { get; set; } public const int DefaultSize = ; //[Range(1, 10000)]
public int Page { get; set; } //[Range(1,100)]
public int Size { get; set; } public void Normalize()
{
if (this.Page <= )
this.Page = ;
if (this.Size <= )
{
var appSettings = IocManager.Instance.Resolve<AppSettings>();
this.Size = appSettings.PageSize;
}
}
}
最后附上中间使用过的两个工具类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;
}
}
/// <summary>
/// 数据转换帮助类
/// </summary>
public static class ConvertHelper
{
#region = ChangeType =
public static object ChangeType(object obj, Type conversionType)
{
return ChangeType(obj, conversionType, Thread.CurrentThread.CurrentCulture);
}
public static object ChangeType(object obj, Type conversionType, IFormatProvider provider)
{
#region Nullable
Type nullableType = Nullable.GetUnderlyingType(conversionType);
if (nullableType != null)
{
if (obj == null)
{
return null;
}
return Convert.ChangeType(obj, nullableType, provider);
}
#endregion
if (typeof(System.Enum).IsAssignableFrom(conversionType))
{
return Enum.Parse(conversionType, obj.ToString());
}
return Convert.ChangeType(obj, conversionType, provider);
}
#endregion
}
如何优雅的使用AbpSettings的更多相关文章
- [Egret]优雅的写http
首先,自从使用链式调用的写法后,就一发不可收拾的喜爱上了这种优雅的方式.不管是写架构还是写模块,我都会不自觉的使用这种最优雅的方式.链式写法既减少了代码量,又非常优雅的. 在使用 egret 的htt ...
- 如何优雅地使用Sublime Text
Sublime Text:一款具有代码高亮.语法提示.自动完成且反应快速的编辑器软件,不仅具有华丽的界面,还支持插件扩展机制,用她来写代码,绝对是一种享受.相比于难于上手的Vim,浮肿沉重的Eclip ...
- 阿里巴巴最新开源项目 - [HandyJSON] 在Swift中优雅地处理JSON
项目名称:HandyJSON 项目地址:https://github.com/alibaba/handyjson 背景 JSON是移动端开发常用的应用层数据交换协议.最常见的场景便是,客户端向服务端发 ...
- doT js 模板引擎【初探】要优雅不要污
js中拼接html,总是感觉不够优雅,本着要优雅不要污,决定尝试js模板引擎. JavaScript 模板引擎 JavaScript 模板引擎作为数据与界面分离工作中最重要一环,越来越受开发者关注. ...
- PostCSS一种更优雅、更简单的书写CSS方式
Sass团队创建了Compass大大提升CSSer的工作效率,你无需考虑各种浏览器前缀兼,只需要按官方文档的书写方式去写,会得到加上浏览器前缀的代码,如下: .row { @include displ ...
- 【swift学习笔记】五.使用枚举优雅的管理Segue
在做页面转跳的时候,我们要给Segue命名,如果Segue多了,管理他们就是一个恶梦.我们可以枚举更优雅的管理这些Segue. 1.我们先来建立一个protocol,他的功能就是让实现类实现一个Seg ...
- ASP.NET Core 优雅的在开发环境保存机密(User Secrets)
前言 在应用程序开发的过程中,有的时候需要在代码中保存一些机密的信息,比如加密密钥,字符串,或者是用户名密码等.通常的做法是保存到一个配置文件中,在以前我们会把他保存到web.config中,但是在A ...
- 【优雅代码】深入浅出 妙用Javascript中apply、call、bind
这篇文章实在是很难下笔,因为网上相关文章不胜枚举. 巧合的是前些天看到阮老师的一篇文章的一句话: “对我来说,博客首先是一种知识管理工具,其次才是传播工具.我的技术文章,主要用来整理我还不懂的知识.我 ...
- 让你的JS更优雅的小技巧
首先,看一个非常不优雅的例子: 看到这段代码,虽然代码很短,但是一眼看上去就不想再看了,也就是没什么可读性.这段代码,没有封装,随意定义一个变量都是全局变量,这样在多人开发或者是大型开发中,极其容易造 ...
随机推荐
- 吴裕雄--天生自然PYTHON学习笔记:解决ElementNotInteractableException: Message: element not interactable
submit=self.wait.until(EC.element_to_be_clickable((By.ID,'loginAction'))) 2.永久覆盖element来保证自己的element ...
- docker 不同引擎导致历史垃圾镜像无法自动清除,致硬盘空间报警
查看硬盘占用大户是/var/lib/docker/vfs/dir 直觉是images文件,历史原因累积了大量的image docker rmi 清除掉不用的image文件 可用空间有提升但提升不大 / ...
- HUPO|PSI|PeptideAtlas|TPP|Partial submission|Complete submission|proteomeXchange
蛋白质组实验数据提交 需要共享数据,共享要求: 质谱实验数据 HUPO Proteomics Standards Initiative (http://www.psidev.info/overview ...
- Users组权限Win7虚拟机继承Administrator的个性化设置
在administrator账号下进行的模板设置,配置文件保存在“C:\Documents and Settings\Administrator”文件夹下的profile里面,但是创建的用户虚拟机获取 ...
- 使用Cron表达式创建定时任务
CronTriggerCronTrigger功能非常强大,是基于日历的作业调度,而SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用.CroTr ...
- unittest(6)- 作业- 测试类中写多个函数
实践作业:对多个接口发起请求,测试类中写多个测试函数 # 1. http_request import requests class HttpRequest: def http_request(sel ...
- Django学习之路02
静态文件配置 html文件默认全都放在templates文件夹下 对于前段已经写好了的文件, 我们只是拿过来使用 那么这些文件都可以称之为叫"静态文件"静态文件可以是 bootst ...
- 初入 Ubuntu 的一些操作 · Lei's blog
查看系统版本 cat /etc/os-release 修改 root 密码 passwd 新建用户 新建用户: adduser username 将新用户加入 sudo 组,这样就可以用 sudo 命 ...
- 用shell脚本新建文件并自动生成头说明信息
目标: 新建文件后,直接给文件写入下图信息 代码实现: [root@localhost test]# vi AutoHead.sh #!/bin/bash #此程序的功能是新建shell文件并自动生成 ...
- Python如何规避全局解释器锁(GIL)带来的限制
编程语言分类概念介绍(编译型语言.解释型语言.静态类型语言.动态类型语言概念与区别) https://www.cnblogs.com/zhoug2020/p/5972262.html Python解释 ...