Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/asp.net-core/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 遍历未知对象类型的图并改变某些对象属性_C#_Asp.net Core_Graph Traversal - Fatal编程技术网

C# 遍历未知对象类型的图并改变某些对象属性

C# 遍历未知对象类型的图并改变某些对象属性,c#,asp.net-core,graph-traversal,C#,Asp.net Core,Graph Traversal,具体要求是,如果用户没有特定权限,则替换某些MVC模型属性中的某些值 该解决方案应该是通用的,适用于任何模型的任何图形,并且也相当有效,因为它将用于屏蔽大型对象列表中的值 这些假设是: 该图由未知类型的自定义对象组成(只知道这些对象是C#类) 所有感兴趣的对象都有公共属性,只应检查公共属性(可能使用自定义[SensitiveData]属性作为过滤器,主要是出于性能原因,忽略大多数属性) 这些对象可能有其他子对象,需要为[SensitiveData]属性遍历这些子对象 对象可能具有需要遍历的不同

具体要求是,如果用户没有特定权限,则替换某些MVC模型属性中的某些值

该解决方案应该是通用的,适用于任何模型的任何图形,并且也相当有效,因为它将用于屏蔽大型对象列表中的值

这些假设是:

  • 该图由未知类型的自定义对象组成(只知道这些对象是C#类)
  • 所有感兴趣的对象都有公共属性,只应检查公共属性(可能使用自定义
    [SensitiveData]
    属性作为过滤器,主要是出于性能原因,忽略大多数属性)
  • 这些对象可能有其他子对象,需要为
    [SensitiveData]
    属性遍历这些子对象
  • 对象可能具有需要遍历的不同类型集合的属性(IEnumerables、ILists、通用IEnumerables和带有自定义对象的ILists)
  • 这些对象还可能包含.NET核心对象的集合,例如
    IEnumerable
    IEnumerable
    ,我对此不感兴趣,也不应该遍历这些对象
  • 我不想遍历.NET核心对象的属性(不需要检查DateTime的Date属性)
  • 我不想遍历.NET核心对象(不需要检查字符串的每个字符,它是IEnumerable)
  • 在大多数情况下,它将是一棵树,而不是一个图形。尽管如此,对于具有防止陷入无休止循环的保护的图来说,实现它会更安全
这一切似乎都是非常符合逻辑和常见要求的,所以我认为应该有一些通用的、经过良好测试的解决方案,我可以根据自己的情况进行调整,只需传递回调函数,或者传递一些过滤器,以便在开始遍历之前定义感兴趣的属性

然而,到目前为止,我发现的一切都局限于单节点类型,或者实现不允许改变我选择的属性,或者它进入带有反射的深层递归,而没有任何性能考虑

我可以自己实现它,但结果可能是用混乱的递归和反射来重新发明轮子。难道没有什么已经存在并且众所周知的“只是工作”吗


另外,我听说反射SetValue和GetValue方法很慢,我应该更好地将setter和getter缓存为委托,并在再次遇到相同类型时重用它们。我将再次遇到相同的类型,因为它是ASP.NET核心web应用程序。因此,如果我缓存每个感兴趣的setter/getter以备将来重用,就有可能比单纯的反射解决方案获得显著的性能提升。

这需要一些努力,但我有一些行之有效的方法:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class SensitiveDataAttribute : Attribute
{
}

public abstract class PocoGraphPropertyWalker
{
    private enum TypeKind
    {
        Primitive,
        IterablePrimitive,
        Poco,
        IterablePoco
    }

    private class TypeAccessDescriptor
    {
        public TypeAccessor accessor;
        public List<PropertyInfo> primitives;
        public List<PropertyInfo> iterables;
        public List<PropertyInfo> singles;
    }

    private static ConcurrentDictionary<Type, TypeAccessDescriptor> _accessorCache =
        new ConcurrentDictionary<Type, TypeAccessDescriptor>();

    public IEnumerable<object> TraversePocoList(IEnumerable<object> pocos)
    {
        if (pocos == null)
            return null;

        foreach (var poco in pocos)
            TraversePoco(poco);

        return pocos;
    }

    public object TraversePoco(object poco)
    {
        var unwound = Traversal(poco, ChildrenSelector).ToList();

        foreach(var unw in unwound)
            VisitPoco(unw);

        return poco;
    }

    public object VisitPoco(object poco)
    {
        if (poco == null)
            return poco;

        var t = poco.GetType();

        // the registry ignores types that are not POCOs
        var typeDesc = TryGetOrRegisterForType(t);

        if (typeDesc == null)
            return poco;

        // do not attempt to parse Keys and Values as primitives,
        // even if they were specified as such
        if (IsKeyValuePair(t))
            return poco;

        foreach (var prop in typeDesc.primitives)
        {
            var oldValue = typeDesc.accessor[poco, prop.Name];
            var newValue = VisitProperty(poco, oldValue, prop);
            typeDesc.accessor[poco, prop.Name] = newValue;
        }

        return poco;
    }

    protected virtual object VisitProperty(object model,
        object currentValue, PropertyInfo prop)
    {
        return currentValue;
    }

    private IEnumerable<object> Traversal(
            object item,
            Func<object, IEnumerable<object>> children)
    {
        var seen = new HashSet<object>();
        var stack = new Stack<object>();

        seen.Add(item);
        stack.Push(item);
        yield return item;

        while (stack.Count > 0)
        {
            object current = stack.Pop();
            foreach (object newItem in children(current))
            {
                // protect against cyclic refs
                if (!seen.Contains(newItem))
                {
                    seen.Add(newItem);
                    stack.Push(newItem);
                    yield return newItem;
                }
            }
        }
    }

    private IEnumerable<object> ChildrenSelector(object poco)
    {
        if (poco == null)
            yield break;

        var t = poco.GetType();

        // the registry ignores types that are not POCOs
        var typeDesc = TryGetOrRegisterForType(t);

        if (typeDesc == null)
            yield break;

        // special hack for KeyValuePair - FastMember fails to access its Key and Value
        // maybe because it's a struct, not class?
        // and now we have prop accessors stored in singles / primitives
        // so we extract it manually
        if (IsKeyValuePair(t))
        {
            // reverting to good old slow reflection
            var k = t.GetProperty("Key").GetValue(poco, null);
            var v = t.GetProperty("Value").GetValue(poco, null);

            if (k != null)
            {
                foreach (var yp in YieldIfPoco(k))
                    yield return yp;
            }

            if (v != null)
            {
                foreach(var yp in YieldIfPoco(v))
                    yield return yp;
            }
            yield break;
        }

        // registration method should have registered correct singles
        foreach (var single in typeDesc.singles)
        {
             yield return typeDesc.accessor[poco, single.Name];
        }

        // registration method should have registered correct IEnumerables
        // to skip strings as enums and primitives as enums
        foreach (var iterable in typeDesc.iterables)
        {
            if (!(typeDesc.accessor[poco, iterable.Name] is IEnumerable iterVals))
                continue;

            foreach (var iterval in iterVals)
                yield return iterval;
        }
    }

    private IEnumerable<object> YieldIfPoco(object v)
    {
        var myKind = GetKindOfType(v.GetType());
        if (myKind == TypeKind.Poco)
        {
            foreach (var d in YieldDeeper(v))
                yield return d;
        }
        else if (myKind == TypeKind.IterablePoco && v is IEnumerable iterVals)
        {
            foreach (var i in iterVals)
                foreach (var d in YieldDeeper(i))
                    yield return d;
        }
    }

    private IEnumerable<object> YieldDeeper(object o)
    {
        yield return o;

        // going slightly recursive here - might have IEnumerable<IEnumerable<IEnumerable<POCO>>>...
        var chs = Traversal(o, ChildrenSelector);
        foreach (var c in chs)
            yield return c;
    }

    private TypeAccessDescriptor TryGetOrRegisterForType(Type t)
    {
        if (!_accessorCache.TryGetValue(t, out var typeAccessorsDescriptor))
        {
            // blacklist - cannot process dictionary KeyValues
            if (IsBlacklisted(t))
                return null;

            // check if I myself am a real Poco before registering my properties
            var myKind = GetKindOfType(t);

            if (myKind != TypeKind.Poco)
                return null;

            var properties = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            var accessor = TypeAccessor.Create(t);

            var primitiveProps = new List<PropertyInfo>();
            var singlePocos = new List<PropertyInfo>();
            var iterablePocos = new List<PropertyInfo>();

            // now sort all props in subtypes:
            // 1) a primitive value or nullable primitive or string
            // 2) an iterable with primitives (including strings and nullable primitives)
            // 3) a subpoco
            // 4) an iterable with subpocos
            // for our purposes, 1 and 2 are the same - just properties,
            // not needing traversion

            // ignoring non-generic IEnumerable - can't know its inner types
            // and it is not expected to be used in our POCOs anyway
            foreach (var prop in properties)
            {
                var pt = prop.PropertyType;
                var propKind = GetKindOfType(pt);

                // 1) and 2)
                if (propKind == TypeKind.Primitive || propKind == TypeKind.IterablePrimitive)
                    primitiveProps.Add(prop);
                else
                if (propKind == TypeKind.IterablePoco)
                    iterablePocos.Add(prop); //4)
                else
                    singlePocos.Add(prop); // 3)
            }

            typeAccessorsDescriptor = new TypeAccessDescriptor {
                accessor = accessor,
                primitives = primitiveProps,
                singles = singlePocos,
                iterables = iterablePocos
            };

            if (!_accessorCache.TryAdd(t, typeAccessorsDescriptor))
            {
                // if failed add, a parallel process added it, just get it back
                if (!_accessorCache.TryGetValue(t, out typeAccessorsDescriptor))
                    throw new Exception("Failed to get a type descriptor that should exist");
            }
        }

        return typeAccessorsDescriptor;
    }

    private static TypeKind GetKindOfType(Type type)
    {
        // 1) a primitive value or nullable primitive or string
        // 2) an iterable with primitives (including strings and nullable primitives)
        // 3) a subpoco
        // 4) an iterable with subpocos

        // ignoring non-generic IEnumerable - can't know its inner types
        // and it is not expected to be used in our POCOs anyway

        // 1)
        if (IsSimpleType(type))
            return TypeKind.Primitive;

        var ienumerableInterfaces = type.GetInterfaces()
            .Where(x => x.IsGenericType && x.GetGenericTypeDefinition() ==
            typeof(IEnumerable<>)).ToList();

        // add itself, if the property is defined as IEnumerable<x>
        if (type.IsGenericType && type.GetGenericTypeDefinition() ==
            typeof(IEnumerable<>))
            ienumerableInterfaces.Add(type);

        if (ienumerableInterfaces.Any(x =>
                IsSimpleType(x.GenericTypeArguments[0])))
            return TypeKind.IterablePrimitive;

        if (ienumerableInterfaces.Count() != 0)
            // 4) - it was enumerable, but not primitive - maybe POCOs
            return TypeKind.IterablePoco;

        return TypeKind.Poco;
    }

    private static bool IsBlacklisted(Type type)
    {
        return false;
    }

    public static bool IsKeyValuePair(Type type)
    {
        return type.IsGenericType && 
            type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>);
    }

    public static bool IsSimpleType(Type type)
    {
        return
            type.IsPrimitive ||
            new Type[] {
        typeof(string),
        typeof(decimal),
        typeof(DateTime),
        typeof(DateTimeOffset),
        typeof(TimeSpan),
        typeof(Guid)
            }.Contains(type) ||
            type.IsEnum ||
            Convert.GetTypeCode(type) != TypeCode.Object ||
            (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && IsSimpleType(type.GetGenericArguments()[0]))
            ;
    }
}

public class ProjectSpecificDataFilter : PocoGraphPropertyWalker
{
    const string MASK = "******";

    protected override object VisitProperty(object model,
            object currentValue, PropertyInfo prop)
    {
        if (prop.GetCustomAttributes<SensitiveDataAttribute>().FirstOrDefault() == null)
            return currentValue;

        if (currentValue == null || (currentValue is string &&
            string.IsNullOrWhiteSpace((string)currentValue)))
            return currentValue;

        return MASK;
    }
}
[AttributeUsage(AttributeTargets.Property,AllowMultiple=false,Inherited=true)]
公共类敏感数据属性:属性
{
}
公共抽象类PocGraphPropertyWalker
{
私有枚举类型
{
原始的,
伊特雷伯原始,
波科,
伊特拉伯科
}
私有类TypeAccessDescriptor
{
公共类型存取器;
公共列表原语;
公共物品清单;
公开单打名单;
}
私有静态ConcurrentDictionary\u accessorCache=
新的ConcurrentDictionary();
公共IEnumerable TraverseColist(IEnumerable pocos)
{
如果(pocos==null)
返回null;
foreach(poco中的var poco)
TraversePoco(poco);
返回POCO;
}
公共对象遍历EPOCO(对象poco)
{
var unbound=遍历(poco,ChildrenSelector).ToList();
foreach(未缠绕时的var unw)
参观奥林匹克运动会(unw);
返回poco;
}
公共对象访问poco(对象poco)
{
if(poco==null)
返回poco;
var t=poco.GetType();
//注册表忽略不是POCO的类型
var typeDesc=TryGetOrRegisterForType(t);
if(typeDesc==null)
返回poco;
//不要尝试将键和值作为原语进行分析,
//即使它们被指定为
如果(IsKeyValuePair(t))
返回poco;
foreach(typeDesc.primitives中的变量prop)
{
var oldValue=typeDesc.accessor[poco,prop.Name];
var newValue=VisitProperty(poco、oldValue、prop);
typeDesc.accessor[poco,prop.Name]=newValue;
}
返回poco;
}
受保护的虚拟对象访问属性(对象模型,
对象currentValue、PropertyInfo属性)
{
返回当前值;
}
私有IEnumerable遍历(
对象项,
(儿童)
{
var seen=newhashset();
var stack=新堆栈();
见.增加(项目);
堆栈。推送(项目);
收益回报项目;
而(stack.Count>0)
{
当前对象=stack.Pop();
foreach(子对象中的对象newItem(当前))
{
//防止循环引用
如果(!seen.Contains(newItem))
{
参见。添加(新项);
stack.Push(newItem);
收益返回新项目;
}
}
}
}
私有IEnumerable子对象选择器(对象poco)
{
if(poco==null)
屈服断裂;
var t=poco.GetType();
//注册表忽略不是POCO的类型
var typeDesc=TryGetOrRegisterForType(t);
if(typeDesc==null)
屈服断裂;
//KeyValuePair的特殊攻击-FastMember无法访问其密钥和值
//也许是因为它是一个结构,而不是类?
//现在我们有了存储在单体/原语中的道具访问器
//所以我们把它提取出来
enum MyEnum
{
    One = 1,
    Two = 2
}

class A
{
    [SensitiveData]
    public string S { get; set; }
    public int I { get; set; }
    public int? I2 { get; set; }
    public MyEnum Enm { get; set; }
    public MyEnum? Enm1 { get; set; }
    public List<MyEnum> Enm2 { get; set; }
    public List<int> IL1 { get; set; }
    public int?[] IL2 { get; set; }
    public decimal Dc { get; set; }
    public decimal? Dc1 { get; set; }
    public IEnumerable<decimal> Dc3 { get; set; }
    public IEnumerable<decimal?> Dc4 { get; set; }
    public IList<decimal> Dc5 { get; set; }
    public DateTime D { get; set; }
    public DateTime? D2 { get; set; }
    public B Child { get; set; }
    public B[] Children { get; set; }
    public List<B> Children2 { get; set; }
    public IEnumerable<B> Children3 { get; set; }
    public IDictionary<int, int?> PrimDict { get; set; }
    public Dictionary<int, B> PocoDict { get; set; }
    public IDictionary<B, int?> PocoKeyDict { get; set; }
    public Dictionary<int, IEnumerable<B>> PocoDeepDict { get; set; }
}

class B
{
    [SensitiveData]
    public string S { get; set; }
    public int I { get; set; }
    public int? I2 { get; set; }
    public DateTime D { get; set; }
    public DateTime? D2 { get; set; }
    public A Parent { get; set; }
}

class Program
{

    static A root;

    static void Main(string[] args)
    {
        root = new A
        {
            D = DateTime.Now,
            D2 = DateTime.Now,
            I = 10,
            I2 = 20,
            S = "stringy",
            Child = new B
            {
                D = DateTime.Now,
                D2 = DateTime.Now,
                I = 10,
                I2 = 20,
                S = "stringy"
            },
            Children = new B[] {
                new B {
                    D = DateTime.Now,
                    D2 = DateTime.Now,
                    I = 10,
                    I2 = 20,
                    S = "stringy" },
                new B {
                    D = DateTime.Now,
                    D2 = DateTime.Now,
                    I = 10,
                    I2 = 20,
                    S = "stringy" },
            },
            Children2 = new List<B> {
                new B {
                    D = DateTime.Now,
                    D2 = DateTime.Now,
                    I = 10,
                    I2 = 20,
                    S = "stringy" },
                new B {
                    D = DateTime.Now,
                    D2 = DateTime.Now,
                    I = 10,
                    I2 = 20,
                    S = "stringy" },
            },
            PrimDict = new Dictionary<int, int?> {
                { 1, 2 },
                { 3, 4 }
            },
            PocoDict = new Dictionary<int, B> {
                { 1,  new B {
                    D = DateTime.Now,
                    D2 = DateTime.Now,
                    I = 10,
                    I2 = 20,
                    S = "stringy" } },
                { 3, new B {
                    D = DateTime.Now,
                    D2 = DateTime.Now,
                    I = 10,
                    I2 = 20,
                    S = "stringy" } }
            },
            PocoKeyDict = new Dictionary<B, int?> {
                { new B {
                    D = DateTime.Now,
                    D2 = DateTime.Now,
                    I = 10,
                    I2 = 20,
                    S = "stringy" }, 1 },
                { new B {
                    D = DateTime.Now,
                    D2 = DateTime.Now,
                    I = 10,
                    I2 = 20,
                    S = "stringy" }, 3 }
            },

            PocoDeepDict = new Dictionary<int, IEnumerable<B>>
            {
                { 1, new [] { new B {D = DateTime.Now,
                    D2 = DateTime.Now,
                    I = 10,
                    I2 = 20,
                    S = "stringy" } } }
            }
        };

        // add cyclic ref for test
        root.Child.Parent = root;

        var f = new VtuaSensitiveDataFilter();
        var r = f.TraversePoco(root);
    }
}