1. 程式人生 > >通過動態構建Expression Select表示式並建立動態型別來控制Property可見性

通過動態構建Expression Select表示式並建立動態型別來控制Property可見性

## 通過建立動態型別 動態構建Expression Select表示式來控制Property可見性 > 專案中經常遇到的一個場景,根據當前登入使用者許可權,僅返回許可權內可見的內容。參考了很多開源框架,更多的是在`ViewModel`層面硬編碼實現。這種方式太過繁瑣,每個需要相應邏輯的地方都要寫一遍。經過研究,筆者提供另外一種實現,目前已經應用到專案中。這裡記錄一下,也希望能給需要的人提供一個參考。 ### 1、定義用於Property可見性的屬性PermissionAttribute `PermissionAttribute.Permissions`儲存了被授權的許可權列表(假設許可權型別是`string`)。建構函式要求`permissions`不能為空,你可以選擇不在`Property`上使用此屬性(對所有許可權可見),或者傳遞一個空陣列(對所有許可權隱藏)。 ``` /// /// 訪問許可屬性 ///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)] public class PermissionAttribute : Attribute { public readonly IEnumerable Permissions; public PermissionAttribute([NotNull] params string[] permissions) { this.Permissions = permissions.Distinct(); } } ``` ### 2、定義Entity,給個別Property新增PermissionAttribute屬性來控制可見性 `Name`屬性的訪問許可權授權給**`3、4`**許可權,`Cities`授權給`1`許可權,`Id`屬性對所有許可權隱藏,`Code`屬性對所有許可權都是可見的。 ``` /// /// 省份實體 ///
[Table("Province")] public class Province { /// /// 自增主鍵 /// [Key, Permission(new string[0])] public int Id { get; set; } /// /// 省份編碼 /// [StringLength(10)] public string Code { get; set; } /// /// 省份名稱 /// [StringLength(64), Permission("3", "4")] public string Name { get; set; } /// /// 城市列表 ///
[Permission("1")] public List Cities { get; set; } } ``` ### 3、構建表示式 `ExpressionExtensions`類提供了根據授權列表`IEnumerable permissions`構建表示式的方法,並擴充套件一個`SelectPermissionDynamic`方法把`sources`對映為表示式返回的結果型別——動態構建的型別。 ``` public static class ExpressionExtensions { /// /// 根據許可權動態查詢 /// /// /// /// /// public static IQueryable SelectPermissionDynamic(this IQueryable sources, IEnumerable permissions) { var selector = BuildExpression(permissions); return sources.Select(selector); } /// /// 構建表示式 /// /// /// /// public static Expression> BuildExpression(IEnumerable permissions) { Type sourceType = typeof(TSource); Dictionary sourceProperties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(prop => { if (!prop.CanRead) { return false; } var perms = prop.GetCustomAttribute(); return (perms == null || perms.Permissions.Intersect(permissions).Any()); }).ToDictionary(p => p.Name, p => p); Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values); ParameterExpression sourceItem = Expression.Parameter(sourceType, "t"); IEnumerable bindings = dynamicType.GetRuntimeProperties().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType(); return Expression.Lambda>(Expression.MemberInit( Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem); } } ``` 上述程式碼片段呼叫了`LinqRuntimeTypeBuilder.GetDynamicType`方法構建動態型別,下面給出`LinqRuntimeTypeBuilder`的原始碼。 ``` public static class LinqRuntimeTypeBuilder { private static readonly AssemblyName AssemblyName = new AssemblyName() { Name = "LinqRuntimeTypes4iTheoChan" }; private static readonly ModuleBuilder ModuleBuilder; private static readonly Dictionary BuiltTypes = new Dictionary(); static LinqRuntimeTypeBuilder() { ModuleBuilder = AssemblyBuilder.DefineDynamicAssembly(AssemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(AssemblyName.Name); } private static string GetTypeKey(Dictionary fields) { //TODO: optimize the type caching -- if fields are simply reordered, that doesn't mean that they're actually different types, so this needs to be smarter string key = string.Empty; foreach (var field in fields) key += field.Key + ";" + field.Value.Name + ";"; return key; } private const MethodAttributes RuntimeGetSetAttrs = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; public static Type BuildDynamicType([NotNull] Dictionary properties) { if (null == properties) throw new ArgumentNullException(nameof(properties)); if (0 == properties.Count) throw new ArgumentOutOfRangeException(nameof(properties), "fields must have at least 1 field definition"); try { // Acquires an exclusive lock on the specified object. Monitor.Enter(BuiltTypes); string className = GetTypeKey(properties); if (BuiltTypes.ContainsKey(className)) return BuiltTypes[className]; TypeBuilder typeBdr = ModuleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable); foreach (var prop in properties) { var propertyBdr = typeBdr.DefineProperty(name: prop.Key, attributes: PropertyAttributes.None, returnType: prop.Value, parameterTypes: null); var fieldBdr = typeBdr.DefineField("itheofield_" + prop.Key, prop.Value, FieldAttributes.Private); MethodBuilder getMethodBdr = typeBdr.DefineMethod("get_" + prop.Key, RuntimeGetSetAttrs, prop.Value, Type.EmptyTypes); ILGenerator getIL = getMethodBdr.GetILGenerator(); getIL.Emit(OpCodes.Ldarg_0); getIL.Emit(OpCodes.Ldfld, fieldBdr); getIL.Emit(OpCodes.Ret); MethodBuilder setMethodBdr = typeBdr.DefineMethod("set_" + prop.Key, RuntimeGetSetAttrs, null, new Type[] { prop.Value }); ILGenerator setIL = setMethodBdr.GetILGenerator(); setIL.Emit(OpCodes.Ldarg_0); setIL.Emit(OpCodes.Ldarg_1); setIL.Emit(OpCodes.Stfld, fieldBdr); setIL.Emit(OpCodes.Ret); propertyBdr.SetGetMethod(getMethodBdr); propertyBdr.SetSetMethod(setMethodBdr); } BuiltTypes[className] = typeBdr.CreateType(); return BuiltTypes[className]; } catch { throw; } finally { Monitor.Exit(BuiltTypes); } } private static string GetTypeKey(IEnumerable fields) { return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType)); } public static Type GetDynamicType(IEnumerable fields) { return BuildDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType)); } } ``` ### 4、測試呼叫 在`Controller`中增加一個`Action`,查詢`DBContext.Provinces`,並用上面擴充套件的`SelectPermissionDynamic`方法對映到動態型別返回當前使用者許可權範圍內可見的內容。程式碼片段如下: ``` [HttpGet, Route(nameof(Visibility))] public IActionResult Visibility(string id) { var querable = _dbContext.Provinces.SelectPermissionDynamic(id.Split(',')).Take(2); return Json(querable.ToList()); } ``` **測試`case`:** 訪問`/Test/Visibility?id=2,3`,預期返回`Code`和`Name`屬性; 訪問`/Test/Visibility?id=8,9`,預期返回`Code`屬性; 如下圖所示,返回符合預期,測試通過! ![測試通過!](https://img2020.cnblogs.com/blog/824443/202102/824443-20210201185741541-699147347.png) > 參考文件: > [1] https://docs.microsoft.com/zh-cn/dotnet/api/system.reflection.emit.assemblybuilder.definedynamicassembly?view=net-5.0 > [2] https://stackoverflow.com/questions/606104/how-to-create-linq-expression-tree-to-select-an-anonymous-type > 原文:https://www.cnblogs.com/itheo/p/14358495.html 作者:Theo·Chan
版權:本文版權歸作者和部落格園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段宣告;必須在文章中給出原文連線;否則必究法律責