Asp.Net Core Identity 騷斷腿的究極魔改實體類
前言
預設的 Identity 實體型別在大多數時候已經基本夠用,很多時候也只是稍微在 IdentityUser 類中增加一些自定義資料欄位,比如頭像。這次,我要向園友隆重介紹我魔改之後的 Identity 實體類,能支援一些特別風騷的操作。當然也完全相容內建的 UserManager、RoleManager 和 SignInManager,畢竟也是從內建型別繼承擴展出來的。
正文
魔改的實體類基於一組我自定義實體介面,這組介面我也實現了一組打包好的基礎型別。因為 Identity 系列實體型別已經存在,而 C# 不支援多重繼承,所以只能把這些程式碼在魔改的 Identity 實體類中貼上幾次了。
先來看看這些基本介面吧:
1 /// <summary> 2 /// 軟刪除介面 3 /// </summary> 4 public interface ILogicallyDeletable 5 { 6 /// <summary> 7 /// 邏輯刪除標記 8 /// </summary> 9 bool IsDeleted { get; set; } 10 } 11 12 /// <summary> 13 /// 活動狀態標記介面 14 /// </summary> 15 public interface IActiveControllable 16 { 17 /// <summary> 18 /// 活動狀態標記 19 /// </summary> 20 bool? Active { get; set; } 21 } 22 23 /// <summary> 24 /// 樂觀併發介面 25 /// </summary> 26 public interface IOptimisticConcurrencySupported 27 { 28 /// <summary> 29 /// 行版本,樂觀併發鎖 30 /// </summary> 31 [ConcurrencyCheck] 32 string ConcurrencyStamp { get; set; } 33 } 34 35 /// <summary> 36 /// 插入順序記錄介面 37 /// </summary> 38 public interface IStorageOrderRecordable 39 { 40 /// <summary> 41 /// 非自增順序欄位作為主鍵型別 42 /// 應該在此列建立聚集索引避免隨機的欄位值導致資料庫索引效能下降 43 /// 同時儲存資料插入先後的資訊 44 /// </summary> 45 long InsertOrder { get; set; } 46 } 47 48 /// <summary> 49 /// 建立時間記錄介面 50 /// </summary> 51 public interface ICreationTimeRecordable 52 { 53 /// <summary> 54 /// 實體建立時間 55 /// </summary> 56 DateTimeOffset CreationTime { get; set; } 57 } 58 59 /// <summary> 60 /// 最後修改時間記錄介面 61 /// </summary> 62 public interface ILastModificationTimeRecordable 63 { 64 /// <summary> 65 /// 最後一次修改時間 66 /// </summary> 67 DateTimeOffset LastModificationTime { get; set; } 68 } 69 70 /// <summary> 71 /// 建立人id記錄介面 72 /// </summary> 73 /// <typeparam name="TIdentityKey">建立人主鍵型別</typeparam> 74 public interface ICreatorRecordable<TIdentityKey> 75 where TIdentityKey : struct, IEquatable<TIdentityKey> 76 { 77 /// <summary> 78 /// 建立人Id 79 /// </summary> 80 TIdentityKey? CreatorId { get; set; } 81 } 82 83 /// <summary> 84 /// 建立人記錄介面 85 /// </summary> 86 /// <typeparam name="TIdentityKey">建立人主鍵型別</typeparam> 87 /// <typeparam name="TIdentityUser">建立人型別</typeparam> 88 public interface ICreatorRecordable<TIdentityKey, TIdentityUser> : ICreatorRecordable<TIdentityKey> 89 where TIdentityKey : struct , IEquatable<TIdentityKey> 90 where TIdentityUser : IEntity<TIdentityKey> 91 { 92 /// <summary> 93 /// 建立人 94 /// </summary> 95 TIdentityUser Creator { get; set; } 96 } 97 98 /// <summary> 99 /// 上次修改人id記錄介面 100 /// </summary> 101 /// <typeparam name="TIdentityKey">上次修改人主鍵型別</typeparam> 102 public interface ILastModifierRecordable<TIdentityKey> 103 where TIdentityKey : struct, IEquatable<TIdentityKey> 104 { 105 /// <summary> 106 /// 上一次修改人Id 107 /// </summary> 108 TIdentityKey? LastModifierId { get; set; } 109 } 110 111 /// <summary> 112 /// 上次修改人記錄介面 113 /// </summary> 114 /// <typeparam name="TIdentityKey">上次修改人主鍵型別</typeparam> 115 /// <typeparam name="TIdentityUser">上次修改人型別</typeparam> 116 public interface ILastModifierRecordable<TIdentityKey, TIdentityUser> : ILastModifierRecordable<TIdentityKey> 117 where TIdentityKey : struct, IEquatable<TIdentityKey> 118 where TIdentityUser : IEntity<TIdentityKey> 119 { 120 /// <summary> 121 /// 上一次修改人 122 /// </summary> 123 TIdentityUser LastModifier { get; set; } 124 }
這些基本介面每一個都對應了一個基本功能。還有一個稍微複雜的樹形資料結構介面:
1 /// <summary> 2 /// 樹形資料介面 3 /// </summary> 4 /// <typeparam name="T">節點資料型別</typeparam> 5 public interface ITree<T> 6 { 7 /// <summary> 8 /// 父節點 9 /// </summary> 10 T Parent { get; set; } 11 12 /// <summary> 13 /// 子節點集合 14 /// </summary> 15 IList<T> Children { get; set; } 16 17 /// <summary> 18 /// 節點深度,根的深度為0 19 /// </summary> 20 int Depth { get; } 21 22 /// <summary> 23 /// 是否是根節點 24 /// </summary> 25 bool IsRoot { get; } 26 27 /// <summary> 28 /// 是否是葉節點 29 /// </summary> 30 bool IsLeaf { get; } 31 32 /// <summary> 33 /// 是否有子節點 34 /// </summary> 35 bool HasChildren { get; } 36 37 /// <summary> 38 /// 節點路徑(UNIX路徑格式,以“/”分隔) 39 /// </summary> 40 string Path { get; } 41 }
然後是打包介面,主要是把基本介面打包到一個統一介面,方便批量使用:
1 /// <summary> 2 /// 實體介面 3 /// </summary> 4 public interface IEntity {} 5 6 /// <summary> 7 /// 泛型實體介面,約束Id屬性 8 /// </summary> 9 public interface IEntity<TKey> : IEntity 10 where TKey : IEquatable<TKey> 11 { 12 TKey Id { get; set; } 13 } 14 15 /// <summary> 16 /// 領域實體介面,主要是整合各個小介面 17 /// </summary> 18 public interface IDomainEntity : IEntity 19 , ILogicallyDeletable 20 , ICreationTimeRecordable 21 , ILastModificationTimeRecordable 22 , INotifyPropertyChanged 23 , INotifyPropertyChangedExtension 24 , IPropertyChangeTrackable 25 {} 26 27 /// <summary> 28 /// 泛型領域實體介面 29 /// </summary> 30 public interface IDomainEntity<TKey> : IEntity<TKey> 31 , IDomainEntity 32 where TKey : struct, IEquatable<TKey> 33 {}View Code
樹形資料結構也有一套:
1 /// <summary> 2 /// 樹形實體介面 3 /// </summary> 4 /// <typeparam name="T">實體型別</typeparam> 5 public interface ITreeEntity<T> : IEntity, ITree<T> 6 { 7 } 8 9 /// <summary> 10 /// 樹形實體介面 11 /// </summary> 12 /// <typeparam name="TKey">主鍵型別</typeparam> 13 /// <typeparam name="TEntity">實體型別</typeparam> 14 public interface ITreeEntity<TKey, TEntity> : ITreeEntity<TEntity>, IEntity<TKey> 15 where TKey : IEquatable<TKey> 16 where TEntity : ITreeEntity<TKey, TEntity> 17 { 18 } 19 20 /// <summary> 21 /// 樹形領域實體介面 22 /// </summary> 23 /// <typeparam name="T">資料型別</typeparam> 24 public interface IDomainTreeEntity<T> : 25 IDomainEntity 26 , ITreeEntity<T> 27 { 28 } 29 30 /// <summary> 31 /// 樹形領域實體介面 32 /// </summary> 33 /// <typeparam name="TKey">主鍵型別</typeparam> 34 /// <typeparam name="TEntity">樹形實體型別</typeparam> 35 public interface IDomainTreeEntity<TKey, TEntity> : 36 IDomainTreeEntity<TEntity> 37 , IDomainEntity<TKey> 38 , ITreeEntity<TKey, TEntity> 39 40 where TKey : struct, IEquatable<TKey> 41 where TEntity : IDomainTreeEntity<TKey, TEntity> 42 { 43 TKey? ParentId { get; set; } 44 }View Code
最後還有幾個特別用處的介面:
1 /// <summary> 2 /// 跟蹤屬性的變更 3 /// </summary> 4 public interface IPropertyChangeTrackable 5 { 6 /// <summary> 7 /// 判斷指定的屬性或任意屬性是否被變更過 8 /// </summary> 9 /// <param name="names">指定要判斷的屬性名陣列,如果為空(null)或空陣列則表示判斷任意屬性</param> 10 /// <returns> 11 /// <para>如果指定的<paramref name="names"/>引數有值,當只有引數中指定的屬性發生過更改則返回真(True),否則返回假(False)</para> 12 /// <para>如果指定的<paramref name="names"/>引數為空(null)或空陣列,當實體中任意屬性發生過更改則返回真(True),否則返回假(False)</para> 13 /// </returns> 14 bool HasChanges(params string[] names); 15 16 /// <summary> 17 /// 獲取實體中發生過變更的屬性集 18 /// </summary> 19 /// <returns>如果實體沒有屬性發生過變更,則返回空白字典,否則返回被變更過的屬性鍵值對</returns> 20 IDictionary<string, object> GetChanges(); 21 22 /// <summary> 23 /// 重置指定的屬性或任意屬性變更狀態(為未變更) 24 /// </summary> 25 /// <param name="names">指定要重置的屬性名陣列,如果為空(null)或空陣列則表示重置所有屬性的變更狀態(為未變更)</param> 26 void ResetPropertyChangeStatus(params string[] names); 27 } 28 29 /// <summary> 30 /// 多對多導航實體介面 31 /// </summary> 32 /// <typeparam name="TIdentityKey">身份實體主鍵型別</typeparam> 33 /// <typeparam name="TIdentityUser">身份實體型別</typeparam> 34 public interface IManyToManyReferenceEntity<TIdentityKey, TIdentityUser> : IManyToManyReferenceEntity<TIdentityKey> 35 , ICreatorRecordable<TIdentityKey, TIdentityUser> 36 where TIdentityKey : struct, IEquatable<TIdentityKey> 37 where TIdentityUser : IEntity<TIdentityKey> 38 { 39 } 40 41 /// <summary> 42 /// 多對多導航實體介面 43 /// </summary> 44 /// <typeparam name="TIdentityKey">身份實體主鍵型別</typeparam> 45 public interface IManyToManyReferenceEntity<TIdentityKey> : IManyToManyReferenceEntity 46 , ICreatorRecordable<TIdentityKey> 47 where TIdentityKey : struct, IEquatable<TIdentityKey> 48 { 49 } 50 51 /// <summary> 52 /// 多對多導航實體介面 53 /// </summary> 54 public interface IManyToManyReferenceEntity : IEntity 55 , ICreationTimeRecordable 56 { 57 }View Code
至此,基本上用到的介面就定義好了,接下來就是魔改 Identity 實體類,這裡以 IdentityRole 為例,其他的可以到我的專案中檢視,大同小異:
1 public class ApplicationRole : ApplicationRole<int, ApplicationUser, ApplicationRole, ApplicationUserRole, ApplicationRoleClaim> 2 , IStorageOrderRecordable 3 { 4 public ApplicationRole() { } 5 public ApplicationRole(string roleName) => Name = roleName; 6 7 public virtual long InsertOrder { get; set; } 8 } 9 10 public abstract class ApplicationRole<TKey, TIdentityUser, TIdentityRole, TUserRole, TRoleClaim> : IdentityRole<TKey> 11 , IDomainTreeEntity<TKey, TIdentityRole> 12 , IOptimisticConcurrencySupported 13 , ICreatorRecordable<TKey, TIdentityUser> 14 , ILastModifierRecordable<TKey, TIdentityUser> 15 where TKey : struct, IEquatable<TKey> 16 where TIdentityUser : IEntity<TKey> 17 where TUserRole : ApplicationUserRole<TKey, TIdentityUser, TIdentityRole> 18 where TRoleClaim : ApplicationRoleClaim<TKey, TIdentityUser, TIdentityRole> 19 where TIdentityRole : ApplicationRole<TKey, TIdentityUser, TIdentityRole, TUserRole, TRoleClaim> 20 { 21 #region 重寫基類屬性使屬性變更通知事件生效 22 23 public override TKey Id { get => base.Id; set => base.Id = value; } 24 public override string ConcurrencyStamp { get => base.ConcurrencyStamp; set => base.ConcurrencyStamp = value; } 25 public override string Name { get => base.Name; set => base.Name = value; } 26 public override string NormalizedName { get => base.NormalizedName; set => base.NormalizedName = value; } 27 28 #endregion 29 30 public string Description { get; set; } 31 32 /// <summary> 33 /// 需要使用.Include(r => r.UserRoles).ThenInclude(ur => ur.Role)預載入或啟用延遲載入 34 /// </summary> 35 [NotMapped] 36 public virtual IEnumerable<TIdentityUser> Users => UserRoles?.Select(ur => ur.User); 37 38 #region 導航屬性 39 40 public virtual List<TUserRole> UserRoles { get; set; } = new List<TUserRole>(); 41 42 public virtual List<TRoleClaim> RoleClaims { get; set; } = new List<TRoleClaim>(); 43 44 #endregion 45 46 #region IDomainTreeEntity成員 47 48 public virtual TKey? ParentId { get; set; } 49 50 #endregion 51 52 #region IEntity成員 53 54 public virtual bool? Active { get; set; } = true; 55 public virtual bool IsDeleted { get; set; } 56 public virtual DateTimeOffset CreationTime { get; set; } = DateTimeOffset.Now; 57 public virtual DateTimeOffset LastModificationTime { get; set; } = DateTimeOffset.Now; 58 59 #endregion 60 61 #region IDomainEntity成員 62 63 public virtual TKey? CreatorId { get; set; } 64 public virtual TIdentityUser Creator { get; set; } 65 public virtual TKey? LastModifierId { get; set; } 66 public virtual TIdentityUser LastModifier { get; set; } 67 68 #endregion 69 70 #region ITree成員 71 72 public virtual TIdentityRole Parent { get; set; } 73 74 public virtual IList<TIdentityRole> Children { get; set; } 75 76 [DoNotNotify, NotMapped] 77 public virtual int Depth => Parent?.Depth + 1 ?? 0; 78 79 [DoNotNotify, NotMapped] 80 public virtual bool IsRoot => Parent == null; 81 82 [DoNotNotify, NotMapped] 83 public virtual bool IsLeaf => Children?.Count == 0; 84 85 [DoNotNotify, NotMapped] 86 public virtual bool HasChildren => !IsLeaf; 87 88 [DoNotNotify, NotMapped] 89 public virtual string Path => Parent == null ? Id.ToString() : $@"{Parent.Path}/{Id}"; 90 91 #endregion 92 93 #region IPropertyChangeTrackable成員 94 95 private static readonly object Locker = new object(); 96 private static readonly Dictionary<Type, string[]> PropertyNamesDictionary = new Dictionary<Type, string[]>(); 97 98 private readonly BitArray _propertyChangeMask; 99 100 /// <summary> 101 /// 全域性屬性變更通知事件處理器 102 /// </summary> 103 public static PropertyChangedEventHandler PublicPropertyChangedEventHandler { get; set; } 104 105 /// <summary> 106 /// 初始化用於跟蹤屬性變更所需的屬性資訊 107 /// </summary> 108 protected ApplicationRole() 109 { 110 //判斷型別是否已經加入字典 111 //將未加入的型別新增進去(一般為該類物件首次初始化時) 112 var type = this.GetType(); 113 if (!PropertyNamesDictionary.ContainsKey(type)) 114 { 115 lock (Locker) 116 { 117 if (!PropertyNamesDictionary.ContainsKey(type)) 118 { 119 PropertyNamesDictionary.Add(type, type.GetProperties() 120 .OrderBy(property => property.Name) 121 .Select(property => property.Name).ToArray()); 122 } 123 } 124 } 125 126 //初始化屬性變更掩碼 127 _propertyChangeMask = new BitArray(PropertyNamesDictionary[type].Length, false); 128 129 //註冊全域性屬性變更事件處理器 130 if (PublicPropertyChangedEventHandler != null) 131 { 132 PropertyChanged += PublicPropertyChangedEventHandler; 133 } 134 } 135 136 /// <summary> 137 /// 屬性變更事件 138 /// </summary> 139 public event PropertyChangedEventHandler PropertyChanged; 140 public event PropertyChangedExtensionEventHandler PropertyChangedExtension; 141 142 /// <summary> 143 /// 內部屬性變更事件處理器 144 /// </summary> 145 /// <param name="propertyName">屬性名</param> 146 /// <param name="oldValue">舊值</param> 147 /// <param name="newValue">新值</param> 148 protected virtual void OnPropertyChanged(string propertyName, object oldValue, object newValue) 149 { 150 //Perform property validation 151 152 _propertyChangeMask[Array.IndexOf(PropertyNamesDictionary[this.GetType()], propertyName)] = true; 153 154 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 155 PropertyChangedExtension?.Invoke(this, new PropertyChangedExtensionEventArgs(propertyName, oldValue, newValue)); 156 } 157 158 /// <summary> 159 /// 判斷指定的屬性或任意屬性是否被變更過(<see cref="IPropertyChangeTrackable"/>介面的實現) 160 /// </summary> 161 /// <param name="names">指定要判斷的屬性名陣列,如果為空(null)或空陣列則表示判斷任意屬性。</param> 162 /// <returns> 163 /// <para>如果指定的<paramref name="names"/>引數有值,當只有引數中指定的屬性發生過更改則返回真(True),否則返回假(False);</para> 164 /// <para>如果指定的<paramref name="names"/>引數為空(null)或空陣列,當實體中任意屬性發生過更改則返回真(True),否則返回假(False)。</para> 165 /// </returns> 166 public bool HasChanges(params string[] names) 167 { 168 if (!(names?.Length > 0)) 169 { 170 foreach (bool mask in _propertyChangeMask) 171 { 172 if (mask == true) 173 { 174 return true; 175 } 176 } 177 178 return false; 179 } 180 181 var type = this.GetType(); 182 foreach (var name in names) 183 { 184 var index = Array.IndexOf(PropertyNamesDictionary[type], name); 185 if (index >= 0 && _propertyChangeMask[index] == true) 186 { 187 return true; 188 } 189 } 190 191 return false; 192 } 193 194 /// <summary> 195 /// 獲取實體中發生過變更的屬性集(<see cref="IPropertyChangeTrackable"/>介面的實現) 196 /// </summary> 197 /// <returns>如果實體沒有屬性發生過變更,則返回空白字典,否則返回被變更過的屬性鍵值對</returns> 198 public IDictionary<string, object> GetChanges() 199 { 200 Dictionary<string, object> changeDictionary = new Dictionary<string, object>(); 201 var type = this.GetType(); 202 for (int i = 0; i < _propertyChangeMask.Length; i++) 203 { 204 if (_propertyChangeMask[i] == true) 205 { 206 changeDictionary.Add(PropertyNamesDictionary[type][i], 207 type.GetProperty(PropertyNamesDictionary[type][i])?.GetValue(this)); 208 } 209 } 210 211 return changeDictionary; 212 } 213 214 /// <summary> 215 /// 重置指定的屬性或任意屬性變更狀態(為未變更)(<see cref="IPropertyChangeTrackable"/>介面的實現) 216 /// </summary> 217 /// <param name="names">指定要重置的屬性名陣列,如果為空(null)或空陣列則表示重置所有屬性的變更狀態(為未變更)</param> 218 public void ResetPropertyChangeStatus(params string[] names) 219 { 220 if (names?.Length > 0) 221 { 222 var type = this.GetType(); 223 foreach (var name in names) 224 { 225 var index = Array.IndexOf(PropertyNamesDictionary[type], name); 226 if (index >= 0) 227 { 228 _propertyChangeMask[index] = false; 229 } 230 } 231 } 232 else 233 { 234 _propertyChangeMask.SetAll(false); 235 } 236 } 237 238 #endregion 239 }View Code
可以看到我在為 IdentityRole 新增介面實現的時候新增的是 IDomainTreeEntity 介面。在這裡我把 Role 改成了樹形資料型別,也就是說一個角色可以是另一個角色的子角色,構成樹狀關係。當然如果就當作普通的 Role 來使用也沒有任何問題,這個擴充套件完全不會破壞任何內建功能,沒有任何侵入性,按需選用就好,至於能發揮什麼作用,完全看腦洞有多大 (●'◡'●)
然而,這還不是全部,不然就對不起魔改的名號了。現在看見的程式碼還不是最終形態。因為使用了 PropertyChanged.Fody 這個庫,所有的實體都可以向外傳送屬性變更通知,至於能發揮什麼作用,還是看腦洞。
程式碼最終形態預覽(此處使用了 ILSpy 反編譯引擎的 Nuget 包,詳情見我之前的部落格C# 編譯器 和 反編譯器,你要哪個(歪頭)? 我全都要(捏拳)!):
魔改部分還不止這些,但是和我接下來打算介紹的部分存在重疊,所以剩下的部分就和接下來的介紹放在一起了,會新開一篇部落格。
各位觀眾老爺對我的魔改實體類有什麼感想歡迎評論交流。可以到下方我的 Github 儲存庫下載專案執行體驗效果。
轉載請完整保留以下內容並在顯眼位置標註,未經授權刪除以下內容進行轉載盜用的,保留追究法律責任的權利!
本文地址:https://www.cnblogs.com/coredx/p/12310010.html
完整原始碼:Github
裡面有各種小東西,這只是其中之一,不嫌棄的話可以Star一