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 ...
随机推荐
- 洛谷P1220 关路灯 题解 区间DP
题目链接:https://www.luogu.com.cn/problem/P1220 本题涉及算法:区间DP. 我们一开始要做一些初始化操作,令: \(p[i]\) 表示第i个路灯的位置: \(w[ ...
- 02_css3.0 前端长度单位 px em rem vm vh vm pc pt in 你真的懂了吗?
1:废话不多说,直接看如下图表: 2:px就不过多介绍了,就是像素点的大小,加入您的屏幕分辨率为1920,则每一个相当于每一个有横着的1920个像素点: 3:em 为相对单位,一般以 body 内的 ...
- 《C++Primer》第五版习题答案--第二章【学习笔记】
C++Primer第五版习题解答---第二章 ps:答案是个人在学习过程中书写,可能存在错漏之处,仅作参考. 作者:cosefy Date: 2020/1/9 第二章:变量和基本类型 练习2.1: 类 ...
- Yolo V3整体思路流程详解!
结合开源项目tensorflow-yolov3(https://link.zhihu.com/?target=https%3A//github.com/YunYang1994/tensorflow-y ...
- cogs 3. 服务点设置 dijkstra
3. 服务点设置 ★ 输入文件:djsa.in 输出文件:djsa.out 简单对比时间限制:1 s 内存限制:128 MB [问题描述] 为了进一步普及九年义务教育,政府要在某乡镇建 ...
- 曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- NOIP复活了
众所周知,NOIP以一种奇葩而又不可避免的方式(CSP)复活了.
- Linux初始化Git环境
第一步:设置Git全局用户名和邮箱 git config --global user.name "你的用户名" git config --global user.email &qu ...
- Spring(二)核心容器 - 简介 、BeanFactory、ApplicationContext
目录 前言 1.容器简介 2.容器的结构 2.1 BeanFactory 2.2 ApplicationContext 2.2.1 ConfigurableApplicationContext 2.2 ...
- Scrapy深度和优先级
一.深度 配置文件 settings.py DEPTH_LIMIT = 5 二.优先级 配置文件 DEPTH_PRIORITY=1 优先级为正数时,随着深度越大,优先级越低 源码中,优先级 reque ...