Asp.Net Core Identity 骚断腿的究极魔改实体类
前言
默认的 Identity 实体类型在大多数时候已经基本够用,很多时候也只是稍微在 IdentityUser 类中增加一些自定义数据字段,比如头像。这次,我要向园友隆重介绍我魔改之后的 Identity 实体类,能支持一些特别风骚的操作。当然也完全兼容内置的 UserManager、RoleManager 和 SignInManager,毕竟也是从内置类型继承扩展出来的。
正文
魔改的实体类基于一组我自定义实体接口,这组接口我也实现了一组打包好的基础类型。因为 Identity 系列实体类型已经存在,而 C# 不支持多重继承,所以只能把这些代码在魔改的 Identity 实体类中粘贴几次了。
先来看看这些基本接口吧:
/// <summary>
/// 软删除接口
/// </summary>
public interface ILogicallyDeletable
{
/// <summary>
/// 逻辑删除标记
/// </summary>
bool IsDeleted { get; set; }
} /// <summary>
/// 活动状态标记接口
/// </summary>
public interface IActiveControllable
{
/// <summary>
/// 活动状态标记
/// </summary>
bool? Active { get; set; }
} /// <summary>
/// 乐观并发接口
/// </summary>
public interface IOptimisticConcurrencySupported
{
/// <summary>
/// 行版本,乐观并发锁
/// </summary>
[ConcurrencyCheck]
string ConcurrencyStamp { get; set; }
} /// <summary>
/// 插入顺序记录接口
/// </summary>
public interface IStorageOrderRecordable
{
/// <summary>
/// 非自增顺序字段作为主键类型
/// 应该在此列建立聚集索引避免随机的字段值导致数据库索引性能下降
/// 同时保存数据插入先后的信息
/// </summary>
long InsertOrder { get; set; }
} /// <summary>
/// 创建时间记录接口
/// </summary>
public interface ICreationTimeRecordable
{
/// <summary>
/// 实体创建时间
/// </summary>
DateTimeOffset CreationTime { get; set; }
} /// <summary>
/// 最后修改时间记录接口
/// </summary>
public interface ILastModificationTimeRecordable
{
/// <summary>
/// 最后一次修改时间
/// </summary>
DateTimeOffset LastModificationTime { get; set; }
} /// <summary>
/// 创建人id记录接口
/// </summary>
/// <typeparam name="TIdentityKey">创建人主键类型</typeparam>
public interface ICreatorRecordable<TIdentityKey>
where TIdentityKey : struct, IEquatable<TIdentityKey>
{
/// <summary>
/// 创建人Id
/// </summary>
TIdentityKey? CreatorId { get; set; }
} /// <summary>
/// 创建人记录接口
/// </summary>
/// <typeparam name="TIdentityKey">创建人主键类型</typeparam>
/// <typeparam name="TIdentityUser">创建人类型</typeparam>
public interface ICreatorRecordable<TIdentityKey, TIdentityUser> : ICreatorRecordable<TIdentityKey>
where TIdentityKey : struct , IEquatable<TIdentityKey>
where TIdentityUser : IEntity<TIdentityKey>
{
/// <summary>
/// 创建人
/// </summary>
TIdentityUser Creator { get; set; }
} /// <summary>
/// 上次修改人id记录接口
/// </summary>
/// <typeparam name="TIdentityKey">上次修改人主键类型</typeparam>
public interface ILastModifierRecordable<TIdentityKey>
where TIdentityKey : struct, IEquatable<TIdentityKey>
{
/// <summary>
/// 上一次修改人Id
/// </summary>
TIdentityKey? LastModifierId { get; set; }
} /// <summary>
/// 上次修改人记录接口
/// </summary>
/// <typeparam name="TIdentityKey">上次修改人主键类型</typeparam>
/// <typeparam name="TIdentityUser">上次修改人类型</typeparam>
public interface ILastModifierRecordable<TIdentityKey, TIdentityUser> : ILastModifierRecordable<TIdentityKey>
where TIdentityKey : struct, IEquatable<TIdentityKey>
where TIdentityUser : IEntity<TIdentityKey>
{
/// <summary>
/// 上一次修改人
/// </summary>
TIdentityUser LastModifier { get; set; }
}
这些基本接口每一个都对应了一个基本功能。还有一个稍微复杂的树形数据结构接口:
/// <summary>
/// 树形数据接口
/// </summary>
/// <typeparam name="T">节点数据类型</typeparam>
public interface ITree<T>
{
/// <summary>
/// 父节点
/// </summary>
T Parent { get; set; } /// <summary>
/// 子节点集合
/// </summary>
IList<T> Children { get; set; } /// <summary>
/// 节点深度,根的深度为0
/// </summary>
int Depth { get; } /// <summary>
/// 是否是根节点
/// </summary>
bool IsRoot { get; } /// <summary>
/// 是否是叶节点
/// </summary>
bool IsLeaf { get; } /// <summary>
/// 是否有子节点
/// </summary>
bool HasChildren { get; } /// <summary>
/// 节点路径(UNIX路径格式,以“/”分隔)
/// </summary>
string Path { get; }
}
然后是打包接口,主要是把基本接口打包到一个统一接口,方便批量使用:
/// <summary>
/// 实体接口
/// </summary>
public interface IEntity {} /// <summary>
/// 泛型实体接口,约束Id属性
/// </summary>
public interface IEntity<TKey> : IEntity
where TKey : IEquatable<TKey>
{
TKey Id { get; set; }
} /// <summary>
/// 领域实体接口,主要是整合各个小接口
/// </summary>
public interface IDomainEntity : IEntity
, ILogicallyDeletable
, ICreationTimeRecordable
, ILastModificationTimeRecordable
, INotifyPropertyChanged
, INotifyPropertyChangedExtension
, IPropertyChangeTrackable
{} /// <summary>
/// 泛型领域实体接口
/// </summary>
public interface IDomainEntity<TKey> : IEntity<TKey>
, IDomainEntity
where TKey : struct, IEquatable<TKey>
{}
树形数据结构也有一套:
/// <summary>
/// 树形实体接口
/// </summary>
/// <typeparam name="T">实体类型</typeparam>
public interface ITreeEntity<T> : IEntity, ITree<T>
{
} /// <summary>
/// 树形实体接口
/// </summary>
/// <typeparam name="TKey">主键类型</typeparam>
/// <typeparam name="TEntity">实体类型</typeparam>
public interface ITreeEntity<TKey, TEntity> : ITreeEntity<TEntity>, IEntity<TKey>
where TKey : IEquatable<TKey>
where TEntity : ITreeEntity<TKey, TEntity>
{
} /// <summary>
/// 树形领域实体接口
/// </summary>
/// <typeparam name="T">数据类型</typeparam>
public interface IDomainTreeEntity<T> :
IDomainEntity
, ITreeEntity<T>
{
} /// <summary>
/// 树形领域实体接口
/// </summary>
/// <typeparam name="TKey">主键类型</typeparam>
/// <typeparam name="TEntity">树形实体类型</typeparam>
public interface IDomainTreeEntity<TKey, TEntity> :
IDomainTreeEntity<TEntity>
, IDomainEntity<TKey>
, ITreeEntity<TKey, TEntity> where TKey : struct, IEquatable<TKey>
where TEntity : IDomainTreeEntity<TKey, TEntity>
{
TKey? ParentId { get; set; }
}
最后还有几个特别用处的接口:
/// <summary>
/// 跟踪属性的变更
/// </summary>
public interface IPropertyChangeTrackable
{
/// <summary>
/// 判断指定的属性或任意属性是否被变更过
/// </summary>
/// <param name="names">指定要判断的属性名数组,如果为空(null)或空数组则表示判断任意属性</param>
/// <returns>
/// <para>如果指定的<paramref name="names"/>参数有值,当只有参数中指定的属性发生过更改则返回真(True),否则返回假(False)</para>
/// <para>如果指定的<paramref name="names"/>参数为空(null)或空数组,当实体中任意属性发生过更改则返回真(True),否则返回假(False)</para>
/// </returns>
bool HasChanges(params string[] names); /// <summary>
/// 获取实体中发生过变更的属性集
/// </summary>
/// <returns>如果实体没有属性发生过变更,则返回空白字典,否则返回被变更过的属性键值对</returns>
IDictionary<string, object> GetChanges(); /// <summary>
/// 重置指定的属性或任意属性变更状态(为未变更)
/// </summary>
/// <param name="names">指定要重置的属性名数组,如果为空(null)或空数组则表示重置所有属性的变更状态(为未变更)</param>
void ResetPropertyChangeStatus(params string[] names);
} /// <summary>
/// 多对多导航实体接口
/// </summary>
/// <typeparam name="TIdentityKey">身份实体主键类型</typeparam>
/// <typeparam name="TIdentityUser">身份实体类型</typeparam>
public interface IManyToManyReferenceEntity<TIdentityKey, TIdentityUser> : IManyToManyReferenceEntity<TIdentityKey>
, ICreatorRecordable<TIdentityKey, TIdentityUser>
where TIdentityKey : struct, IEquatable<TIdentityKey>
where TIdentityUser : IEntity<TIdentityKey>
{
} /// <summary>
/// 多对多导航实体接口
/// </summary>
/// <typeparam name="TIdentityKey">身份实体主键类型</typeparam>
public interface IManyToManyReferenceEntity<TIdentityKey> : IManyToManyReferenceEntity
, ICreatorRecordable<TIdentityKey>
where TIdentityKey : struct, IEquatable<TIdentityKey>
{
} /// <summary>
/// 多对多导航实体接口
/// </summary>
public interface IManyToManyReferenceEntity : IEntity
, ICreationTimeRecordable
{
}
至此,基本上用到的接口就定义好了,接下来就是魔改 Identity 实体类,这里以 IdentityRole 为例,其他的可以到我的项目中查看,大同小异:
public class ApplicationRole : ApplicationRole<int, ApplicationUser, ApplicationRole, ApplicationUserRole, ApplicationRoleClaim>
, IStorageOrderRecordable
{
public ApplicationRole() { }
public ApplicationRole(string roleName) => Name = roleName; public virtual long InsertOrder { get; set; }
} public abstract class ApplicationRole<TKey, TIdentityUser, TIdentityRole, TUserRole, TRoleClaim> : IdentityRole<TKey>
, IDomainTreeEntity<TKey, TIdentityRole>
, IOptimisticConcurrencySupported
, ICreatorRecordable<TKey, TIdentityUser>
, ILastModifierRecordable<TKey, TIdentityUser>
where TKey : struct, IEquatable<TKey>
where TIdentityUser : IEntity<TKey>
where TUserRole : ApplicationUserRole<TKey, TIdentityUser, TIdentityRole>
where TRoleClaim : ApplicationRoleClaim<TKey, TIdentityUser, TIdentityRole>
where TIdentityRole : ApplicationRole<TKey, TIdentityUser, TIdentityRole, TUserRole, TRoleClaim>
{
#region 重写基类属性使属性变更通知事件生效 public override TKey Id { get => base.Id; set => base.Id = value; }
public override string ConcurrencyStamp { get => base.ConcurrencyStamp; set => base.ConcurrencyStamp = value; }
public override string Name { get => base.Name; set => base.Name = value; }
public override string NormalizedName { get => base.NormalizedName; set => base.NormalizedName = value; } #endregion public string Description { get; set; } /// <summary>
/// 需要使用.Include(r => r.UserRoles).ThenInclude(ur => ur.Role)预加载或启用延迟加载
/// </summary>
[NotMapped]
public virtual IEnumerable<TIdentityUser> Users => UserRoles?.Select(ur => ur.User); #region 导航属性 public virtual List<TUserRole> UserRoles { get; set; } = new List<TUserRole>(); public virtual List<TRoleClaim> RoleClaims { get; set; } = new List<TRoleClaim>(); #endregion #region IDomainTreeEntity成员 public virtual TKey? ParentId { get; set; } #endregion #region IEntity成员 public virtual bool? Active { get; set; } = true;
public virtual bool IsDeleted { get; set; }
public virtual DateTimeOffset CreationTime { get; set; } = DateTimeOffset.Now;
public virtual DateTimeOffset LastModificationTime { get; set; } = DateTimeOffset.Now; #endregion #region IDomainEntity成员 public virtual TKey? CreatorId { get; set; }
public virtual TIdentityUser Creator { get; set; }
public virtual TKey? LastModifierId { get; set; }
public virtual TIdentityUser LastModifier { get; set; } #endregion #region ITree成员 public virtual TIdentityRole Parent { get; set; } public virtual IList<TIdentityRole> Children { get; set; } [DoNotNotify, NotMapped]
public virtual int Depth => Parent?.Depth + ?? ; [DoNotNotify, NotMapped]
public virtual bool IsRoot => Parent == null; [DoNotNotify, NotMapped]
public virtual bool IsLeaf => Children?.Count == ; [DoNotNotify, NotMapped]
public virtual bool HasChildren => !IsLeaf; [DoNotNotify, NotMapped]
public virtual string Path => Parent == null ? Id.ToString() : $@"{Parent.Path}/{Id}"; #endregion #region IPropertyChangeTrackable成员 private static readonly object Locker = new object();
private static readonly Dictionary<Type, string[]> PropertyNamesDictionary = new Dictionary<Type, string[]>(); private readonly BitArray _propertyChangeMask; /// <summary>
/// 全局属性变更通知事件处理器
/// </summary>
public static PropertyChangedEventHandler PublicPropertyChangedEventHandler { get; set; } /// <summary>
/// 初始化用于跟踪属性变更所需的属性信息
/// </summary>
protected ApplicationRole()
{
//判断类型是否已经加入字典
//将未加入的类型添加进去(一般为该类对象首次初始化时)
var type = this.GetType();
if (!PropertyNamesDictionary.ContainsKey(type))
{
lock (Locker)
{
if (!PropertyNamesDictionary.ContainsKey(type))
{
PropertyNamesDictionary.Add(type, type.GetProperties()
.OrderBy(property => property.Name)
.Select(property => property.Name).ToArray());
}
}
} //初始化属性变更掩码
_propertyChangeMask = new BitArray(PropertyNamesDictionary[type].Length, false); //注册全局属性变更事件处理器
if (PublicPropertyChangedEventHandler != null)
{
PropertyChanged += PublicPropertyChangedEventHandler;
}
} /// <summary>
/// 属性变更事件
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangedExtensionEventHandler PropertyChangedExtension; /// <summary>
/// 内部属性变更事件处理器
/// </summary>
/// <param name="propertyName">属性名</param>
/// <param name="oldValue">旧值</param>
/// <param name="newValue">新值</param>
protected virtual void OnPropertyChanged(string propertyName, object oldValue, object newValue)
{
//Perform property validation _propertyChangeMask[Array.IndexOf(PropertyNamesDictionary[this.GetType()], propertyName)] = true; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
PropertyChangedExtension?.Invoke(this, new PropertyChangedExtensionEventArgs(propertyName, oldValue, newValue));
} /// <summary>
/// 判断指定的属性或任意属性是否被变更过(<see cref="IPropertyChangeTrackable"/>接口的实现)
/// </summary>
/// <param name="names">指定要判断的属性名数组,如果为空(null)或空数组则表示判断任意属性。</param>
/// <returns>
/// <para>如果指定的<paramref name="names"/>参数有值,当只有参数中指定的属性发生过更改则返回真(True),否则返回假(False);</para>
/// <para>如果指定的<paramref name="names"/>参数为空(null)或空数组,当实体中任意属性发生过更改则返回真(True),否则返回假(False)。</para>
/// </returns>
public bool HasChanges(params string[] names)
{
if (!(names?.Length > ))
{
foreach (bool mask in _propertyChangeMask)
{
if (mask == true)
{
return true;
}
} return false;
} var type = this.GetType();
foreach (var name in names)
{
var index = Array.IndexOf(PropertyNamesDictionary[type], name);
if (index >= && _propertyChangeMask[index] == true)
{
return true;
}
} return false;
} /// <summary>
/// 获取实体中发生过变更的属性集(<see cref="IPropertyChangeTrackable"/>接口的实现)
/// </summary>
/// <returns>如果实体没有属性发生过变更,则返回空白字典,否则返回被变更过的属性键值对</returns>
public IDictionary<string, object> GetChanges()
{
Dictionary<string, object> changeDictionary = new Dictionary<string, object>();
var type = this.GetType();
for (int i = ; i < _propertyChangeMask.Length; i++)
{
if (_propertyChangeMask[i] == true)
{
changeDictionary.Add(PropertyNamesDictionary[type][i],
type.GetProperty(PropertyNamesDictionary[type][i])?.GetValue(this));
}
} return changeDictionary;
} /// <summary>
/// 重置指定的属性或任意属性变更状态(为未变更)(<see cref="IPropertyChangeTrackable"/>接口的实现)
/// </summary>
/// <param name="names">指定要重置的属性名数组,如果为空(null)或空数组则表示重置所有属性的变更状态(为未变更)</param>
public void ResetPropertyChangeStatus(params string[] names)
{
if (names?.Length > )
{
var type = this.GetType();
foreach (var name in names)
{
var index = Array.IndexOf(PropertyNamesDictionary[type], name);
if (index >= )
{
_propertyChangeMask[index] = false;
}
}
}
else
{
_propertyChangeMask.SetAll(false);
}
} #endregion
}
可以看到我在为 IdentityRole 添加接口实现的时候添加的是 IDomainTreeEntity 接口。在这里我把 Role 改成了树形数据类型,也就是说一个角色可以是另一个角色的子角色,构成树状关系。当然如果就当作普通的 Role 来使用也没有任何问题,这个扩展完全不会破坏任何内置功能,没有任何侵入性,按需选用就好,至于能发挥什么作用,完全看脑洞有多大 (●'◡'●)
然而,这还不是全部,不然就对不起魔改的名号了。现在看见的代码还不是最终形态。因为使用了 PropertyChanged.Fody 这个库,所有的实体都可以向外发送属性变更通知,至于能发挥什么作用,还是看脑洞。
代码最终形态预览(此处使用了 ILSpy 反编译引擎的 Nuget 包,详情见我之前的博客C# 编译器 和 反编译器,你要哪个(歪头)? 我全都要(捏拳)!):

魔改部分还不止这些,但是和我接下来打算介绍的部分存在重叠,所以剩下的部分就和接下来的介绍放在一起了,会新开一篇博客。
各位观众老爷对我的魔改实体类有什么感想欢迎评论交流。可以到下方我的 Github 存储库下载项目运行体验效果。
转载请完整保留以下内容并在显眼位置标注,未经授权删除以下内容进行转载盗用的,保留追究法律责任的权利!
本文地址:https://www.cnblogs.com/coredx/p/12310010.html
完整源代码:Github
里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。
Asp.Net Core Identity 骚断腿的究极魔改实体类的更多相关文章
- IdentityServer(12)- 使用 ASP.NET Core Identity
IdentityServer具有非常好的扩展性,其中用户及其数据(包括密码)部分你可以使用任何想要的数据库进行持久化. 如果需要一个新的用户数据库,那么ASP.NET Core Identity是你的 ...
- ASP.NET Core Identity Hands On(1)——Identity 初次体验
ASP.NET Core Identity是用于构建ASP.NET Core Web应用程序的成员资格系统,包括成员资格.登录和用户数据存储 这是来自于 ASP.NET Core Identity 仓 ...
- ASP.NET Core Identity Hands On(2)——注册、登录、Claim
上一篇文章(ASP.NET Core Identity Hands On(1)--Identity 初次体验)中,我们初识了Identity,并且详细分析了AspNetUsers用户存储表,这篇我们将 ...
- IdentityServer4 中文文档 -14- (快速入门)使用 ASP.NET Core Identity
IdentityServer4 中文文档 -14- (快速入门)使用 ASP.NET Core Identity 原文:http://docs.identityserver.io/en/release ...
- IdentityServer4【QuickStart】之使用asp.net core Identity
使用asp.net core Identity IdentityServer灵活的设计中有一部分是可以将你的用户和他们的数据保存到数据库中的.如果你以一个新的用户数据库开始,那么,asp.net co ...
- ASP.NET Core Identity 实战(2)——注册、登录、Claim
上一篇文章(ASP.NET Core Identity Hands On(1)--Identity 初次体验)中,我们初识了Identity,并且详细分析了AspNetUsers用户存储表,这篇我们将 ...
- ASP.NET Core Identity 实战(4)授权过程
这篇文章我们将一起来学习 Asp.Net Core 中的(注:这样描述不准确,稍后你会明白)授权过程 前情提要 在之前的文章里,我们有提到认证和授权是两个分开的过程,而且认证过程不属于Identity ...
- 用一个应用场景理解ASP.NET Core Identity是什么?
目录 前言 基于声明的认证(Claims-based Authentication) 应用场景一 在ASP.NET Core 中Identity是如何实现的 类ClaimsPrincipal 考察另外 ...
- 用例子看ASP.NET Core Identity是什么?
原文:用例子看ASP.NET Core Identity是什么? 目录 前言 基于声明的认证(Claims-based Authentication) Claim 在ASP.NET Core Iden ...
随机推荐
- 阿里云ECS单节点Kubernetes部署
参考资料: kubernetes官网英文版 kubernetes官网中文版 环境.工具 阿里云学生机ECS.Ubuntu.docker.kubectl1.15.4.kubelet1.15.4.kube ...
- DispatcherServlet的url-pattern尽量不要配置为"/*"
DispatcherServlet的url-pattern尽量不要配置为"/*" 原因 当Dispatcher的url配置为"/*"时,会把tomcat的web ...
- SpringCloudAlibaba通过jib插件打包发布到docker仓库
序言 在SpringBoot项目部署的时候,我了解到了Jib插件的强大,这个插件可以快速构建镜像发布到我们的镜像仓库当中去.于是我打算在毕设当中加上这个功能,并且整合到github actions中去 ...
- zTree 节点勾选取消勾选 选中取消选中
zTreeObj.cancelSelectedNode function 举例 取消当前所有被选中节点的选中状态 var treeObj = $.fn.zTree.getZTreeObj(" ...
- Java 发展简史:初生遇低谷,崛起于互联网
Java 起源与诞生 20世纪90年代,单片式计算机系统诞生,单片式计算机系统不仅廉价,而且功能强大,使用它可以大幅度提升消费性电子产品的智能化程度. SUN公司为了抢占市场先机,在1991年成立了一 ...
- vue开源Element UI表单设计及代码生成器
在日常的开发工作中,表单开发是较为繁琐且重复的.本文介绍一个我自己写的,提高开发效率的小工具. 1 可视化设计器 设计器基于Element UI ,可通过点击或拖拽的方式设计基本表单, 设计器生成的代 ...
- 我们为什么会删除不了集群的 Namespace?
作者 | 声东 阿里云售后技术专家 导读:阿里云售后技术团队的同学,每天都在处理各式各样千奇百怪的线上问题.常见的有网络连接失败.服务器宕机.性能不达标及请求响应慢等.但如果要评选的话,什么问题看起 ...
- IDEA 2019.2及以下版本永久激活教程(亲测可用)
写在前面 由于最近jetbrains公司开始严厉打击盗版激活码,所以导致一大批激活码失效,我身边的小伙伴对于如此苦恼,但是由于考虑到正版费用还是比较高昂的前提下,所以鉴于此,遂将之前整理的jar包激活 ...
- Java之File类用法总结
File类概述:文件和文件目录路径的抽象表达形式,与平台无关.1.File能新建.删除.重命名文件和目录,但File不能访问文件内容本身.如果需要访问文件内容本身,则需要使用输入/输出流.2.想要在J ...
- MySQL快速回顾:更新和删除操作
前提要述:参考书籍<MySQL必知必会> 6.1 更新数据 为了更新(修改)表中的数据,可使用UPDATE语句.可采用两种方式使用UPDATE: 更新表中特定的行: 更新表中所有的行. U ...