通过动态构建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> 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<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> fields)
{
return GetTypeKey(fields.ToDictionary(f => f.Name, f => f.PropertyType));
} public static Type GetDynamicType(IEnumerable<PropertyInfo> 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,预期返回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. setHeader方法的参数说明

    转自:http://blog.sina.com.cn/s/blog_510fdc8b0100v8sg.html response.setHeader 是用来设置返回页面的头 meta 信息, 使用时 ...

  2. [leetcode]516. Longest Palindromic Subsequence最大回文子序列

    Given a string s, find the longest palindromic subsequence's length in s. You may assume that the ma ...

  3. Mac电脑远程连接SSH Host key verification failed 解决办法

    苹果电脑远程连接SSH出现如下问题: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                     ...

  4. 分析http协议和高并发网站架构

    案例任务名称 分析http协议和高并发网站架构 案例训练目标 深入理解http协议的工作原理 掌握http协议的分析方法 包含技能点 搭建web服务器 编辑简单的html页面并上传到服务器 使用wir ...

  5. LVS之2---基于LVS负载均衡集群架构

    LVS之2---基于LVS负载均衡集群架构实现 目录 LVS之2---基于LVS负载均衡集群架构实现 ipvsadm software package Options 常用命令 保存及重载规则 内存映 ...

  6. 【转载】VUE的背景图引入

    我现在的项目要将登录页面的背景引一图片做为背景图片,按原jsp中的写法,发现无法找到背景图片,最后从网上查资料,采用上面的写法,成功显示出背景图片,参考网址 https://blog.csdn.net ...

  7. 第四章节 BJROBOT 线速度校正 【ROS全开源阿克曼转向智能网联无人驾驶车】

    BJROBOT 线速度校正   1.把小车平放在地板上,用卷尺作为测量刻度,选取车头或者车尾处作为小车的起点, 打开资料里的虚拟机,打开一个终端 ssh 过去主控端启动 roslaunch znjro ...

  8. C# 9 新特性 —— 补充篇

    C# 9 新特性 -- 补充篇 Intro 前面我们分别介绍了一些 C# 9 中的新特性,还有一些我觉得需要了解一下的新特性,写一篇作为补充. Top-Level Statements 在以往的代码里 ...

  9. WEB安全讨论-表单登录是先验证验证码还是密码

    表单登录是先验证验证码还是密码? 肯定是验证码呀!!!这是毋庸置疑的.但是发现有人会验证密码,感觉先验证密码和先验证验证码是一个概念是一样的.但是其实是完全不一样的.下面我们来一起详细的剖析一下: 消 ...

  10. Mac上“您没有权限来打开应用程序”(Big Sur)

    最近电脑更新了Macos的最新11版大苏尔 Big Sur.很快问题就出现了:安装某个软件的时候Key Gen打不开,提示您没有权限来打开应用程序,类似这样:https://zhuanlan.zhih ...