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

通过创建动态类型 动态构建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 smarter
        string 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共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接,否则必究法律责任
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 C# 中,可以使用表达式动态构建 Select 匿名查询。下面是一个简单的示例: 假设有一个名为 Person 的类,有两个属:Name 和 Age。 ```csharp class Person { public string Name { get; set; } public int Age { get; set; } } ``` 现在我们想要查询出所有人的名字和年龄,但是只想返回一个包含这两个属的匿名对象。可以使用表达式树来实现这个功能: ```csharp // 构建表达式树 var parameter = Expression.Parameter(typeof(Person), "p"); var selector = Expression.Lambda( Expression.New( typeof { Name = "", Age = 0 }.GetTypeInfo().DeclaredConstructors.First(), Expression.Property(parameter, "Name"), Expression.Property(parameter, "Age")), parameter); // 执行查询 var people = new List<Person> { new Person { Name = "Alice", Age = 20 }, new Person { Name = "Bob", Age = 30 } }; var result = people.AsQueryable().Select(selector).ToList(); ``` 在上面的示例中,首先使用 Expression.Parameter 方法创建一个参数表达式,然后使用 Expression.Lambda 方法创建一个 Lambda 表达式,这个 Lambda 表达式表示一个转换函数,将 Person 对象转换为匿名对象。其中,Expression.New 方法表示创建一个新的匿名对象,使用 typeof 获取匿名对象的类型信息,DeclaredConstructors.First() 获取匿名对象的构造函数信息,然后使用 Expression.Property 方法获取 Person 对象的 Name 和 Age 属值。 最后,使用 AsQueryable 方法将 List 转换成 IQueryable,然后使用 Select 方法对每个 Person 对象执行转换函数,返回一个包含所有匿名对象的列表。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值