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

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

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

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

  1. ///<summary>
  2. /// 访问许可属性
  3. ///</summary>
  4. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
  5. public class PermissionAttribute : Attribute
  6. {
  7. public readonly IEnumerable<string> Permissions;
  8. public PermissionAttribute([NotNull] params string[] permissions)
  9. {
  10. this.Permissions = permissions.Distinct();
  11. }
  12. }

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

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

  1. ///<summary>
  2. /// 省份实体
  3. ///</summary>
  4. [Table("Province")]
  5. public class Province
  6. {
  7. /// <summary>
  8. /// 自增主键
  9. /// </summary>
  10. [Key, Permission(new string[0])]
  11. public int Id { get; set; }
  12. /// <summary>
  13. /// 省份编码
  14. /// </summary>
  15. [StringLength(10)]
  16. public string Code { get; set; }
  17. /// <summary>
  18. /// 省份名称
  19. /// </summary>
  20. [StringLength(64), Permission("3", "4")]
  21. public string Name { get; set; }
  22. /// <summary>
  23. /// 城市列表
  24. /// </summary>
  25. [Permission("1")]
  26. public List<object> Cities { get; set; }
  27. }

3、构建表达式

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

  1. public static class ExpressionExtensions
  2. {
  3. /// <summary>
  4. /// 根据权限动态查找
  5. /// </summary>
  6. /// <typeparam name="TSource"></typeparam>
  7. /// <param name="sources"></param>
  8. /// <param name="permissions"></param>
  9. /// <returns></returns>
  10. public static IQueryable<object> SelectPermissionDynamic<TSource>(this IQueryable<TSource> sources, IEnumerable<string> permissions)
  11. {
  12. var selector = BuildExpression<TSource>(permissions);
  13. return sources.Select(selector);
  14. }
  15. /// <summary>
  16. /// 构建表达式
  17. /// </summary>
  18. /// <param name="sources"></param>
  19. /// <param name="permissions"></param>
  20. /// <returns></returns>
  21. public static Expression<Func<TSource, object>> BuildExpression<TSource>(IEnumerable<string> permissions)
  22. {
  23. Type sourceType = typeof(TSource);
  24. Dictionary<string, PropertyInfo> sourceProperties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(prop =>
  25. {
  26. if (!prop.CanRead) { return false; }
  27. var perms = prop.GetCustomAttribute<PermissionAttribute>();
  28. return (perms == null || perms.Permissions.Intersect(permissions).Any());
  29. }).ToDictionary(p => p.Name, p => p);
  30. Type dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);
  31. ParameterExpression sourceItem = Expression.Parameter(sourceType, "t");
  32. IEnumerable<MemberBinding> bindings = dynamicType.GetRuntimeProperties().Select(p => Expression.Bind(p, Expression.Property(sourceItem, sourceProperties[p.Name]))).OfType<MemberBinding>();
  33. return Expression.Lambda<Func<TSource, object>>(Expression.MemberInit(
  34. Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), sourceItem);
  35. }
  36. }

上述代码片段调用了LinqRuntimeTypeBuilder.GetDynamicType方法构建动态类型,下面给出LinqRuntimeTypeBuilder的源码。

  1. public static class LinqRuntimeTypeBuilder
  2. {
  3. private static readonly AssemblyName AssemblyName = new AssemblyName() { Name = "LinqRuntimeTypes4iTheoChan" };
  4. private static readonly ModuleBuilder ModuleBuilder;
  5. private static readonly Dictionary<string, Type> BuiltTypes = new Dictionary<string, Type>();
  6. static LinqRuntimeTypeBuilder()
  7. {
  8. ModuleBuilder = AssemblyBuilder.DefineDynamicAssembly(AssemblyName, AssemblyBuilderAccess.Run).DefineDynamicModule(AssemblyName.Name);
  9. }
  10. private static string GetTypeKey(Dictionary<string, Type> fields)
  11. {
  12. //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
  13. string key = string.Empty;
  14. foreach (var field in fields)
  15. key += field.Key + ";" + field.Value.Name + ";";
  16. return key;
  17. }
  18. private const MethodAttributes RuntimeGetSetAttrs = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
  19. public static Type BuildDynamicType([NotNull] Dictionary<string, Type> properties)
  20. {
  21. if (null == properties)
  22. throw new ArgumentNullException(nameof(properties));
  23. if (0 == properties.Count)
  24. throw new ArgumentOutOfRangeException(nameof(properties), "fields must have at least 1 field definition");
  25. try
  26. {
  27. // Acquires an exclusive lock on the specified object.
  28. Monitor.Enter(BuiltTypes);
  29. string className = GetTypeKey(properties);
  30. if (BuiltTypes.ContainsKey(className))
  31. return BuiltTypes[className];
  32. TypeBuilder typeBdr = ModuleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);
  33. foreach (var prop in properties)
  34. {
  35. var propertyBdr = typeBdr.DefineProperty(name: prop.Key, attributes: PropertyAttributes.None, returnType: prop.Value, parameterTypes: null);
  36. var fieldBdr = typeBdr.DefineField("itheofield_" + prop.Key, prop.Value, FieldAttributes.Private);
  37. MethodBuilder getMethodBdr = typeBdr.DefineMethod("get_" + prop.Key, RuntimeGetSetAttrs, prop.Value, Type.EmptyTypes);
  38. ILGenerator getIL = getMethodBdr.GetILGenerator();
  39. getIL.Emit(OpCodes.Ldarg_0);
  40. getIL.Emit(OpCodes.Ldfld, fieldBdr);
  41. getIL.Emit(OpCodes.Ret);
  42. MethodBuilder setMethodBdr = typeBdr.DefineMethod("set_" + prop.Key, RuntimeGetSetAttrs, null, new Type[] { prop.Value });
  43. ILGenerator setIL = setMethodBdr.GetILGenerator();
  44. setIL.Emit(OpCodes.Ldarg_0);
  45. setIL.Emit(OpCodes.Ldarg_1);
  46. setIL.Emit(OpCodes.Stfld, fieldBdr);
  47. setIL.Emit(OpCodes.Ret);
  48. propertyBdr.SetGetMethod(getMethodBdr);
  49. propertyBdr.SetSetMethod(setMethodBdr);
  50. }
  51. BuiltTypes[className] = typeBdr.CreateType();
  52. return BuiltTypes[className];
  53. }
  54. catch
  55. {
  56. throw;
  57. }
  58. finally
  59. {
  60. Monitor.Exit(BuiltTypes);
  61. }
  62. }
  63. private static string GetTypeKey(IEnumerable<PropertyInfo> fields)
  64. {
  65. return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType));
  66. }
  67. public static Type GetDynamicType(IEnumerable<PropertyInfo> fields)
  68. {
  69. return BuildDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType));
  70. }
  71. }

4、测试调用

Controller中增加一个Action,查询DBContext.Provinces,并用上面扩展的SelectPermissionDynamic方法映射到动态类型返回当前用户权限范围内可见的内容。代码片段如下:

  1. [HttpGet, Route(nameof(Visibility))]
  2. public IActionResult Visibility(string id)
  3. {
  4. var querable = _dbContext.Provinces.SelectPermissionDynamic(id.Split(',')).Take(2);
  5. return Json(querable.ToList());
  6. }

测试case

访问/Test/Visibility?id=2,3,预期返回CodeName属性;

访问/Test/Visibility?id=5,6,预期返回Code属性;

如下图所示,返回符合预期,测试通过!

参考文档: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

版权:本文版权归作者和博客园共有

转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

通过动态构建Expression Select表达式并创建动态类型来控制Property可见性的更多相关文章

  1. [C#.NET 拾遗补漏]13:动态构建LINQ查询表达式

    最近工作中遇到一个这样的需求:在某个列表查询功能中,可以选择某个数字列(如商品单价.当天销售额.当月销售额等),再选择 小于或等于 和 大于或等于 ,再填写一个待比较的数值,对数据进行查询过滤. 如果 ...

  2. javascript如何解析json对javascript如何解析json对象并动态赋值到select列表象并动态赋值到select列表

    原文 javascript如何解析json对象并动态赋值到select列表 JSON(JavaScriptObject Notation)一种简单的数据格式,比xml更轻巧.JSON是JavaScri ...

  3. expression select表达式动态构建

    参考: http://blog.csdn.net/tastelife/article/details/7340205 http://blog.csdn.net/sweety820/article/de ...

  4. C# 动态构建表达式树(一)—— 构建 Where 的 Lambda 表达式

    C# 动态构建表达式树(一)-- 构建 Where 的 Lambda 表达式 前言 记得之前同事在做筛选功能的时候提出过一个问题:如果用户传入的条件数量不确定,条件的内容也不确定(大于.小于和等于), ...

  5. 动态生成C# Lambda表达式

    转载:http://www.educity.cn/develop/1407905.html,并整理! 对于C# Lambda的理解我们在之前的文章中已经讲述过了,那么作为Delegate的进化使用,为 ...

  6. C# 动态构建表达式树(二)——构建 Select 和 GroupBy 的表达式

    C# 动态构建表达式树(二)--构建 Select 和 GroupBy 的表达式 前言 在上篇中写了表达式的基本使用,为 Where 方法动态构建了表达式.在这篇中会写如何为 Select 和 Gro ...

  7. 分享动态拼接Expression表达式组件及原理

    前言 LINQ大家都知道,用起来也还不错,但有一个问题,当你用Linq进行搜索的时候,你是这样写的 var query = from user in db.Set<User>()      ...

  8. Lind.DDD.ExpressionExtensions动态构建表达式树,实现对数据集的权限控制

    回到目录 Lind.DDD框架里提出了对数据集的控制,某些权限的用户为某些表添加某些数据集的权限,具体实现是在一张表中存储用户ID,表名,检索字段,检索值和检索操作符,然后用户登陆后,通过自己权限来构 ...

  9. C# Lambda 表达式学习之(三):动态构建类似于 c => c.Age == null || c.Age > 18 的表达式

    可能你还感兴趣: 1. C# Lambda 表达式学习之(一):得到一个类的字段(Field)或属性(Property)名,强类型得到 2. C# Lambda 表达式学习之(二):LambdaExp ...

随机推荐

  1. Android驱动学习-APP操作新硬件的两种方法(支持添加的驱动)

    在给Android添加新的驱动后,app要如何使用呢? 正常的使用一个设备,需要getService.但是像LED等我们自己添加的硬件驱动,Android源代码根本没有我们自己添加的服务. 第一种: ...

  2. rocketmq部署架构

    1 技术架构 RocketMQ架构上主要分为四部分,如上图所示: Producer:消息发布的角色,支持分布式集群方式部署.Producer通过MQ的负载均衡模块选择相应的Broker集群队列进行消息 ...

  3. web.xml中配置启动时加载的servlet,load-on-starup

    web.xml中配置启动时加载的servlet,load-on-starup 使用servlet来初始化配置文件数据: 在servlet的配置当中,<load-on-startup>1&l ...

  4. OneBlog开源博客-详细介绍如何实现freemarker自定义标签

    前言 OneBlog中使用到了springboot + freemarker的技术,同时项目里多个controller中都需要查询一个公有的数据集合,一般做法是直接在每个controller的方法中通 ...

  5. 手摸手带你用Hexo撸博客(三)之添加评论系统

    原文地址 注: 笔者采用的是butterfly主题, 主题内置集成评论系统 butterfly主题开启评论 开启评论需要在comments-use中填写你需要的评论. 以Valine为例 commen ...

  6. 图解SparkStreaming与Kafka的整合,这些细节大家要注意!

    前言 老刘是一名即将找工作的研二学生,写博客一方面是复习总结大数据开发的知识点,一方面是希望帮助更多自学的小伙伴.由于老刘是自学大数据开发,肯定会存在一些不足,还希望大家能够批评指正,让我们一起进步! ...

  7. 一行 CSS 代码的魅力

    之前在知乎看到一个很有意思的讨论 一行代码可以做什么? 那么,一行 CSS 代码又能不能搞点事情呢? CSS Battle 首先,这让我想到了,年初的时候沉迷的一个网站 CSS Battle .这个网 ...

  8. 园子的品牌专区上新:NoSQL 数据库佼佼者 Aerospike

    品牌专区是园子去年推出的新楼盘,为优秀的科技企业在园子里提供一个地方,展示自己的品牌,分享自己的技术内容. 最近我们和国外领先的 NoSQL 数据库厂商 Aerospike 达成了合作,入驻了园子的品 ...

  9. java:原子类的CAS

    当一个处理器想要更新某个变量的值时,向总线发出LOCK#信号,此时其他处理器的对该变量的操作请求将被阻塞,发出锁定信号的处理器将独占共享内存,于是更新就是原子性的了. 1.compareAndSet- ...

  10. vue的路由组件挂载。

    vue通过多种方式可以将组件挂载到一个页面上.挂载方式有四种.其实也并不止四种.这里呢就简单的提四种方式去怎样挂载组件. 第一种就是作为标签形式挂载.前面也提到. 后面的就是一般的挂载组件和按需挂载组 ...