Abp vNext 番外篇-疑难杂症丨浅谈扩展属性与多用户设计
说明
Abp vNext基础篇的文章还差一个单元测试模块就基本上完成了我争取10.1放假之前给大家赶稿出来,后面我们会开始进阶篇,开始拆一些东西,具体要做的事我会单独开一个文章来讲
缘起
本篇文章缘起于dyAbp大佬们在给夏琳儿(简称:小富婆)
讲解技术的时候发起,因为多用户设计和用户扩展属性设计在社区已经是一个每天都会有人来问一遍的问题,这里浅谈一下我的理解,源码是根据EasyAbp作者Super
写的代码,根据我自己的理解去分析的想法。
扩展属性
先从我们单用户系统来讲,如果我该如何扩展用户属性?
在Abp默认解决方案Domain.Shared
中更改ConfigureExtraProperties
,该操作会向IdentityUser
实体添加SocialSecurityNumber
属性
public static void ConfigureExtraProperties()
{
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance.Modules()
.ConfigureIdentity(identity =>
{
identity.ConfigureUser(user =>
{
user.AddOrUpdateProperty<string>( //property type: string
"SocialSecurityNumber", //property name
property =>
{
//validation rules
property.Attributes.Add(new RequiredAttribute());
property.Attributes.Add(
new StringLengthAttribute(64) {
MinimumLength = 4
}
);
//...other configurations for this property
}
);
});
});
});
}
EntityExtensions
还提供了很多配置操作,这里就简单的举几个常用的例子更多详细操作可以在文章下方连接到官方连接。
// 默认值选项
property =>
{
property.DefaultValue = 42;
}
//默认值工厂选项
property =>
{
property.DefaultValueFactory = () => DateTime.Now;
}
// 数据注解属性
property =>
{
property.Attributes.Add(new RequiredAttribute());
property.Attributes.Add(new StringLengthAttribute(64) {MinimumLength = 4});
}
//验证操作
property =>
{
property.Attributes.Add(new RequiredAttribute());
property.Attributes.Add(new StringLengthAttribute(64) {MinimumLength = 4});
property.Validators.Add(context =>
{
if (((string) context.Value).StartsWith("B"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Social security number can not start with the letter 'B', sorry!",
new[] {"extraProperties.SocialSecurityNumber"}
)
);
}
});
}
目前这种配置方式如果你的前端是mvc或者razor pages是不需要改动代码的,页面会动态生成字段,但是如果是angular就需要人工来操作了,除了扩展属性外,你可能还需要部分或完全覆盖某些服务和页面组件才行,不过Abp官方文档都有相应的操作指南所以没有任何问题。
具体更多操作官方地址:https://docs.abp.io/en/abp/latest/Module-Entity-Extensions
另外就是大家最关系的数据存储问题,默认我们添加的数据都会在ExtraProperties以JSON对象方式进行存储
但如果你想用字段的方式进行存储的话,可以在你的.EntityFrameworkCore项目的类中写下这个。然后您需要使用标准Add-Migration和Update-Database命令来创建新的数据库迁移并将更改应用到您的数据库。
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
"SocialSecurityNumber",
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(64);
}
);
多用户设计
举例你要开发学生管理系统
- 老师和学生都会进入系统来做自己对应的操作,我们如何来隔离呢?
首先我们就可以想到通过角色来做权限分配做能力隔离
- 然后学生和老师的参数不一样,怎么办,老师要填写工号、系部、教学科目、工龄,学生要填写年度、班级、学号?,看到过比较粗暴的方案就是直接在IdentityUser表全给干上去,但是这种做法相对于某个角色来看是不是太冗余?
这里我参考Super
的一个做法采用使用自己的数据库表/集合创建新实体
,具体什么意思呢?
我们创建Teacher
实体,该实体通过UserId指定IdentityUser
,来存储作为老师的额外属性
public class Teacher : AggregateRoot<Guid>, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
public virtual Guid UserId { get; protected set; }
public virtual bool Active { get; protected set; }
[NotNull]
public virtual string Name { get; protected set; }
public virtual int? Age { get; protected set; }
protected Teacher()
{
}
public Teacher(Guid id, Guid? tenantId, Guid userId, bool active, [NotNull] string name, int? age) : base(id)
{
TenantId = tenantId;
UserId = userId;
Update(active, name, age);
}
public void Update(bool active, [NotNull] string name, int? age)
{
Active = active;
Name = name;
Age = age;
}
}
处理方案是通过订阅UserEto
,这是User预定义的专用事件类,当User产生Created、Updated和Deleted操作收会到通知,然后执行我们自己逻辑,
[UnitOfWork]
public class TeacherUserInfoSynchronizer :
IDistributedEventHandler<EntityCreatedEto<UserEto>>,
IDistributedEventHandler<EntityUpdatedEto<UserEto>>,
IDistributedEventHandler<EntityDeletedEto<UserEto>>,
ITransientDependency
{
private readonly IGuidGenerator _guidGenerator;
private readonly ICurrentTenant _currentTenant;
private readonly IUserRoleFinder _userRoleFinder;
private readonly IRepository<Teacher, Guid> _teacherRepository;
public TeacherUserInfoSynchronizer(
IGuidGenerator guidGenerator,
ICurrentTenant currentTenant,
IUserRoleFinder userRoleFinder,
IRepository<Teacher, Guid> teacherRepository)
{
_guidGenerator = guidGenerator;
_currentTenant = currentTenant;
_userRoleFinder = userRoleFinder;
_teacherRepository = teacherRepository;
}
public async Task HandleEventAsync(EntityCreatedEto<UserEto> eventData)
{
if (!await HasTeacherRoleAsync(eventData.Entity))
{
return;
}
await CreateOrUpdateTeacherAsync(eventData.Entity, true);
}
public async Task HandleEventAsync(EntityUpdatedEto<UserEto> eventData)
{
if (await HasTeacherRoleAsync(eventData.Entity))
{
await CreateOrUpdateTeacherAsync(eventData.Entity, true);
}
else
{
await CreateOrUpdateTeacherAsync(eventData.Entity, false);
}
}
public async Task HandleEventAsync(EntityDeletedEto<UserEto> eventData)
{
await TryUpdateAndDeactivateTeacherAsync(eventData.Entity);
}
protected async Task<bool> HasTeacherRoleAsync(UserEto user)
{
var roles = await _userRoleFinder.GetRolesAsync(user.Id);
return roles.Contains(MySchoolConsts.TeacherRoleName);
}
protected async Task CreateOrUpdateTeacherAsync(UserEto user, bool active)
{
var teacher = await FindTeacherAsync(user);
if (teacher == null)
{
teacher = new Teacher(_guidGenerator.Create(), _currentTenant.Id, user.Id, active, user.Name, null);
await _teacherRepository.InsertAsync(teacher, true);
}
else
{
teacher.Update(active, user.Name, teacher.Age);
await _teacherRepository.UpdateAsync(teacher, true);
}
}
protected async Task TryUpdateAndDeactivateTeacherAsync(UserEto user)
{
var teacher = await FindTeacherAsync(user);
if (teacher == null)
{
return;
}
teacher.Update(false, user.Name, teacher.Age);
await _teacherRepository.UpdateAsync(teacher, true);
}
protected async Task<Teacher> FindTeacherAsync(UserEto user)
{
return await _teacherRepository.FindAsync(x => x.UserId == user.Id);
}
}
结语
我也是在阅读文档和对照Super大佬的代码后自己的理解,文中可能某些地方可能与作者设计有差距,还请大家多多理解!
也欢迎大家阅读我的Abp vNext系列教程
联系作者:加群:867095512 @MrChuJiu
Abp vNext 番外篇-疑难杂症丨浅谈扩展属性与多用户设计的更多相关文章
- 番外篇--Moddule Zero多租户管理
番外篇--Moddule Zero多租户管理 2.1.1 关于多租户 强烈建议阅读这个文件前阅读多租户文档. 2.1.2 启用多租户 ASP.NET Boilerplate和module-zero可以 ...
- 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)
目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...
- iOS冰与火之歌(番外篇) - 基于PEGASUS(Trident三叉戟)的OS X 10.11.6本地提权
iOS冰与火之歌(番外篇) 基于PEGASUS(Trident三叉戟)的OS X 10.11.6本地提权 蒸米@阿里移动安全 0x00 序 这段时间最火的漏洞当属阿联酋的人权活动人士被apt攻击所使用 ...
- 给深度学习入门者的Python快速教程 - 番外篇之Python-OpenCV
这次博客园的排版彻底残了..高清版请移步: https://zhuanlan.zhihu.com/p/24425116 本篇是前面两篇教程: 给深度学习入门者的Python快速教程 - 基础篇 给深度 ...
- 可视化(番外篇)——在Eclipse RCP中玩转OpenGL
最近在看有关Eclipse RCP方面的东西,鉴于Gephi是使用opengl作为绘图引擎,所以,萌生了在Eclipse RCP下添加画布,使用opengl绘图的想法,网上有博文详细介绍这方面的内容, ...
- 可视化(番外篇)——SWT总结
本篇主要介绍如何在SWT下构建一个应用,如何安装SWT Designer并破解已进行SWT的可视化编程,Display以及Shell为何物.有何用,SWT中的常用组件.面板容器以及事件模型等. 1.可 ...
- 【重走Android之路】【番外篇】关于==和equals
[重走Android之路][番外篇]关于==和equals 在实际的编程当中,经常会使用==和equals来判断变量是否相同.但是这两种比较方式也常常让人搞得云里雾里摸不着头脑.下面是我个人做的总 ...
- 【重走Android之路】【番外篇】有关于null的一些知识点
[重走Android之路][番外篇]有关于null的一些知识点 1.首先,到底什么是null? null是Java中的一个关键字,用于表示一个空对象引用,但其本身并不是任何类型也不是属于任何对象. ...
- 番外篇 之 C#委托
对于上一节 番外篇之C#多线程的反思 反思一: Thread th = new Thread(参数); ////参数的总结 ////首先,第一情况,对于 Thread th = new Threa ...
随机推荐
- 【硬件模块】华为NBIOT 使用记录
From: https://liudongdong1.github.io/ 1. background Low power wide area network (LPWAN) has become a ...
- spring框架学习日志一
一.简介 1.对spring框架的简单理解 可以理解为它是一个管理对象的创建.依赖.销毁的容器 Spring 是一个开源框架. Spring 为简化企业级应用开发而生. 使用 Spring 可以使简单 ...
- 如何将eclipse中项目部署到tomcat
项目路径: \tmp0\wtpwebapps\test 复制test目录到 D:\software_install\apache-tomcat-8.0.33-windows-x64\apache-to ...
- 回调与Promise
Promise 对象就是用于表示一个异步操作的最终状态(成功或失败).它的流程就是在什么状态下需要执行什么样的操作. resolve简单理解就是一步操作执行成功后的回调函数 then是Promise对 ...
- win7上帝模式详解
最近,Windows7"GodMode"(上帝模式)被国内各大网站和论坛炒得沸沸扬扬."GodMode"始见于国外网站"GeekInDisguise& ...
- Qt学习日记篇-Qt中使用Curl和jsonCpp
1.Qt中安装并使用jsonCPP库 1.1 官网下载.https://sourceforge.net/projects/jsoncpp/ 解压文件得到 jsoncpp-src-0.5.0 文 ...
- Linux下Sed命令替换文件中的所有IP
命令: sed -ri 's/([0-9]{1,3}\.){3}[0-9]{1,3}/localhost/g' es_create_index.sh 如图:
- adb 常用命令大全(7)- 其他实用功能
屏幕截图 adb exec-out screencap -p > sc.pn 截图保存到电脑执行该命令的目录下 如果指定文件名以 .png 结尾时可以省略 -p 参数 注意 如果 adb 版本较 ...
- 性能测试必备命令(2)- uptime
性能测试必备的 Linux 命令系列,可以看下面链接的文章哦 https://www.cnblogs.com/poloyy/category/1819490.html 介绍 系统启动up了(运行了)多 ...
- openswan协商流程之(五):main_inR2_outI3()
主模式第五包:main_inR2_outI3 文章目录 主模式第五包:main_inR2_outI3 1. 序言 2.函数调用关系 3. 第五个报文流程图 4. main_inR2_outI3()源码 ...