在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的更多相关文章

  1. [Egret]优雅的写http

    首先,自从使用链式调用的写法后,就一发不可收拾的喜爱上了这种优雅的方式.不管是写架构还是写模块,我都会不自觉的使用这种最优雅的方式.链式写法既减少了代码量,又非常优雅的. 在使用 egret 的htt ...

  2. 如何优雅地使用Sublime Text

    Sublime Text:一款具有代码高亮.语法提示.自动完成且反应快速的编辑器软件,不仅具有华丽的界面,还支持插件扩展机制,用她来写代码,绝对是一种享受.相比于难于上手的Vim,浮肿沉重的Eclip ...

  3. 阿里巴巴最新开源项目 - [HandyJSON] 在Swift中优雅地处理JSON

    项目名称:HandyJSON 项目地址:https://github.com/alibaba/handyjson 背景 JSON是移动端开发常用的应用层数据交换协议.最常见的场景便是,客户端向服务端发 ...

  4. doT js 模板引擎【初探】要优雅不要污

    js中拼接html,总是感觉不够优雅,本着要优雅不要污,决定尝试js模板引擎. JavaScript 模板引擎 JavaScript 模板引擎作为数据与界面分离工作中最重要一环,越来越受开发者关注. ...

  5. PostCSS一种更优雅、更简单的书写CSS方式

    Sass团队创建了Compass大大提升CSSer的工作效率,你无需考虑各种浏览器前缀兼,只需要按官方文档的书写方式去写,会得到加上浏览器前缀的代码,如下: .row { @include displ ...

  6. 【swift学习笔记】五.使用枚举优雅的管理Segue

    在做页面转跳的时候,我们要给Segue命名,如果Segue多了,管理他们就是一个恶梦.我们可以枚举更优雅的管理这些Segue. 1.我们先来建立一个protocol,他的功能就是让实现类实现一个Seg ...

  7. ASP.NET Core 优雅的在开发环境保存机密(User Secrets)

    前言 在应用程序开发的过程中,有的时候需要在代码中保存一些机密的信息,比如加密密钥,字符串,或者是用户名密码等.通常的做法是保存到一个配置文件中,在以前我们会把他保存到web.config中,但是在A ...

  8. 【优雅代码】深入浅出 妙用Javascript中apply、call、bind

    这篇文章实在是很难下笔,因为网上相关文章不胜枚举. 巧合的是前些天看到阮老师的一篇文章的一句话: “对我来说,博客首先是一种知识管理工具,其次才是传播工具.我的技术文章,主要用来整理我还不懂的知识.我 ...

  9. 让你的JS更优雅的小技巧

    首先,看一个非常不优雅的例子: 看到这段代码,虽然代码很短,但是一眼看上去就不想再看了,也就是没什么可读性.这段代码,没有封装,随意定义一个变量都是全局变量,这样在多人开发或者是大型开发中,极其容易造 ...

随机推荐

  1. 使用VSCode调试Javascript的三种方式

    Code Runner 在应用商店中搜索Code Runner插件进行安装. 选中你要执行的Javascript脚本,右键选择Run Code,利用Console.log在下方的输出窗口里可以看到输出 ...

  2. Django中查询相关操作

    查询集特性 1)惰性查询:只有在实际使用查询集中的数据的时候才会发生对数据库的真正查询. 2)缓存:当使用的是同一个查询集时,第一次使用的时候会发生实际数据库的查询,然后把结果缓存起来,之后再使用这个 ...

  3. sm3算法的简单介绍

    转自:https://blog.csdn.net/hugewaves/article/details/53765063 SM3算法也是一种哈希算法,中国国家密码管理局在2010年发布,其名称是SM3密 ...

  4. [Python] Advanced features

    Slicing 12345 L[:10:2] # [0, 2, 4, 6, 8]L[::5] # 所有数,每5个取一个# [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, ...

  5. 吴裕雄--天生自然python学习笔记:Python3 MySQL 数据库连接 - PyMySQL 驱动

    什么是 PyMySQL? PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服务器的一个库,Python2中则使用mysqldb. PyMySQL 遵循 Python 数据库 AP ...

  6. VRRP笔记一:基本简介(注意iptables和selinux的问题)

    LAN客户端判定哪个路由器应该为其到达目标主机的下一跳网关的方式有动态及静态决策两种方式,其中,觉的动态路由发现方式有如下几种: 1.Proxy ARP —— 客户端使用ARP协议获取其想要到达的目标 ...

  7. JS数字千分

    JS数字千分: 1.例子:1000--->1,000 2.实现如下: salesToFormat: function (num) { var num = (num || 0).toString( ...

  8. Spring 错误 cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'context:property-placeholder'.

    我来说下这个出错的原因吧 eclise中xsd的验证问题Description Resource Path Location Type cvc-complex-type.2.4.c: The matc ...

  9. HEALER

    项目介绍: 专注是一款时间管理应用,可以帮你管理时间,制定计划,让你保持专注,从快节奏的当下抽离,进入另一个平和安静的时空,以获得更好的工作和学习效率. 主模块(专注):设置分类.专注时长.简介,点击 ...

  10. 不装逼地说,在 Google 到底能学到啥?

    不装逼地说,在 Google 到底能学到啥? 2017-03-17 PHP开发者 (点击上方蓝字,快速关注我们) 本文转自公众号「半轻人」(ID:ban-qing-ren),伯乐在线/PHP开发者已获 ...