通过创建动态类型 动态构建Expression Select表达式来控制Property可见性

项目中经常遇到的一个场景,根据当前登录用户权限,仅返回权限内可见的内容。参考了很多开源框架,更多的是在ViewModel层面硬编码实现。这种方式太过繁琐,每个需要相应逻辑的地方都要写一遍。经过研究,笔者提供另外一种实现,目前已经应用到项目中。这里记录一下,也希望能给需要的人提供一个参考。

1、定义用于Property可见性的属性PermissionAttribute

PermissionAttribute.Permissions保存了被授权的权限列表(假设权限类型是string)。构造函数要求permissions不能为空,你可以选择不在Property上使用此属性(对所有权限可见),或者传递一个空数组(对所有权限隐藏)。

///<summary>
/// 访问许可属性
///</summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
public class PermissionAttribute : Attribute
{public readonly IEnumerable<string> Permissions;public PermissionAttribute([NotNull] params string[] permissions){this.Permissions = permissions.Distinct();}
}

2、定义Entity,给个别Property添加PermissionAttribute属性来控制可见性

Name属性的访问权限授权给**3、4**权限,Cities授权给1权限,Id属性对所有权限隐藏,Code属性对所有权限都是可见的。

///<summary>
/// 省份实体
///</summary>
[Table("Province")]
public class Province
{/// <summary>/// 自增主键/// </summary>[Key, Permission(new string[0])]public int Id { get; set; }/// <summary>/// 省份编码/// </summary>[StringLength(10)]public string Code { get; set; }/// <summary>/// 省份名称/// </summary>[StringLength(64), Permission("3", "4")]public string Name { get; set; }/// <summary>/// 城市列表/// </summary>[Permission("1")]public List<object> Cities { get; set; }
}

3、构建表达式

ExpressionExtensions类提供了根据授权列表IEnumerable<string> permissions构建表达式的方法,并扩展一个SelectPermissionDynamic方法把sources映射为表达式返回的结果类型——动态构建的类型。

public static class ExpressionExtensions
{/// <summary>/// 根据权限动态查找/// </summary>/// <typeparam name="TSource"></typeparam>/// <param name="sources"></param>/// <param name="permissions"></param>/// <returns></returns>public static IQueryable<object> SelectPermissionDynamic<TSource>(this IQueryable<TSource> sources, IEnumerable<string> permissions){var selector = BuildExpression<TSource>(permissions);return sources.Select(selector);}/// <summary>/// 构建表达式/// </summary>/// <param name="sources"></param>/// <param name="permissions"></param>/// <returns></returns>public static Expression<Func<TSource, object>> BuildExpression<TSource>(IEnumerable<string> permissions){Type sourceType = typeof(TSource);Dictionary<string, PropertyInfo> sourceProperties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(prop =>{if (!prop.CanRead) { return false; }var perms = prop.GetCustomAttribute<PermissionAttribute>();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<MemberBinding> bindings = dynamicType.GetRuntimeProperties().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();return Expression.Lambda<Func<TSource, object>>(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<string, Type> BuiltTypes = new Dictionary<string, Type>();static LinqRuntimeTypeBuilder(){ModuleBuilder = AssemblyBuilder.DefineDynamicAssembly(AssemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(AssemblyName.Name);}private static string GetTypeKey(Dictionary<string, Type> properties){//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 smarterstring key = string.Empty;foreach (var prop in properties)key += prop.Key + ";" + prop.Value.Name + ";";return key;}private const MethodAttributes RuntimeGetSetAttrs = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;public static Type BuildDynamicType([NotNull] Dictionary<string, Type> 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<PropertyInfo> properties){return GetTypeKey(properties.ToDictionary(f => f.Name, f => f.PropertyType));}public static Type GetDynamicType(IEnumerable<PropertyInfo> properties){return BuildDynamicType(properties.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,预期返回CodeName属性;
访问/Test/Visibility?id=8,9,预期返回Code属性;
如下图所示,返回符合预期,测试通过!

参考文档:
[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://blog.csdn.net/iTheoChan/article/details/113527246

作者:Theo·Chan
版权:本文为原创内容,版权归作者和CSDN共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接,否则必究法律责任

通过创建动态类型 动态构建Expression Select表达式来控制Property可见性相关推荐

  1. [C#基础知识系列]专题十七:深入理解动态类型

    本专题概要: 动态类型介绍 为什么需要动态类型 动态类型的使用 动态类型背后的故事 动态类型的约束 实现动态行为 总结 引言: 终于迎来了我们C# 4中特性了,C# 4主要有两方面的改善--Com 互 ...

  2. 动态类型+面向对象 = shit

    动态类型+面向对象 = shit 1.动态类型 动态类型语言,属于看起来方便,用后麻烦的东西,我不喜欢它们.除非不需要考虑软件工程场合--如作为脚本语言. 动态类型+高阶函数,使得Scheme定义的o ...

  3. SAP UI5 应用开发教程之五十八 - 使用工厂方法在运行时动态创建不同类型的列表行项目控件试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

  4. var和dynamic的应用 var、动态类型 dynamic 深入浅析C#中的var和dynamic ----demo

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  5. python 动态类型检测 性能_4种速度最慢的动态编程语言,你一定用过

    编程语言已经存在很多年,每种语言都是为了满足特定的需要而构建的. 到今天为止,全世界大约有700种编程语言.现在仅剩250种语言能存续下来,其余的基本上已经被编程社区抛弃了. 对于程序员来说,性能一直 ...

  6. 弱类型、强类型、动态类型、静态类型语言的区别

    类型系统的一些概念,众说纷纭,使用上也比较乱.有些东西,甚至不好严格定义.以下算学术界的一种相对"严格"的说法. 首先了解一下基本概念 Program Errors trapped ...

  7. 静态类型和动态类型的语言有什么区别?

    我听到很多新的编程语言是动态类型的,但是当我们说一种语言是动态类型还是静态类型时,这实际上意味着什么? #1楼 http://en.wikipedia.org/wiki/Type_system 静态打 ...

  8. mysql创建视图,动态传入参数

    一.解释 首先,视图中是不能像mybatis一样使用 # 和 $ 符号传参的,如果想要 动态传入参数,需要结合函数来实现 二.实现方式 1.首选需要创建一个函数,用来接收并赋值要传入的动态参数 cre ...

  9. python动态类型是如何实现的_Python 的动态类型系统

    Python 与 ABC 的一个重要区别在于其类型系统. ABC 采用静态类型,编译器会检查程序中的变量类型是否保持一致,如果不一致,程序就无法运行.并且,ABC与当时大多数静态语言不同,采用的是类型 ...

最新文章

  1. python怎么做彩票概率_用Python一次性把论文作图与数据处理全部搞定!
  2. 【每日一算法】移除元素
  3. 在指定的查找范围内获取DOM元素
  4. 块级格式化上下文(Block Formatting Context)
  5. C语言 03-第一个C程序代码分析
  6. c# 学习笔记 (2) 窗体之间互相调用的方法
  7. 前端使用linux命令更新项目生产包与测试包命令
  8. 【CodeForces - 471C】MUH and House of Cards (思维,找规律)
  9. php 系统模版_原生 PHP 模板系统:Plates
  10. async/await 顺序执行和并行
  11. Bottle: Python Web Framework
  12. 数字资产价值巨大,GMQGroup深入布局挖掘数字财富
  13. flex右对齐_移动WEB开发 — Flex布局
  14. 【渝粤教育】国家开放大学2018年秋季 0242-21T机械制图 参考试题
  15. python语言用什么编译器_如何修改python语言pycharm工具的默认编译器
  16. Linux下实现一个论文翻译阅读的小工具
  17. Noiseware 5 降噪滤镜
  18. 骁龙cpu linux内核,高通骁龙888 SoC在Linux 5.12内核才被支持,以往怎么兼容的?
  19. 如何终止运行matlab,matlab终止运行命令
  20. Selenium WebDriver 数据驱动测试框架

热门文章

  1. 计算机开机最快,你电脑开机要多久?4招让你的电脑开机速度比之前快几倍
  2. luogu 2698 [USACO12MAR]花盆Flowerpot 单调队列
  3. 一篇文章带你认识 Java 异常以及如何处理异常
  4. vue-element-admin 的使用记录(三)
  5. #1文献学习总结---移动边缘计算
  6. vue 项目 去哪儿
  7. PHP移动互联网开发笔记(1)——环境搭建及配置
  8. 基于freeradius的无线认证
  9. Laplace近似后验概率
  10. 开课吧-智能物联网训练营Day2-QT布局和植物与僵尸类构造