I want to consume an ASP.NET Core Web API method that includes [FromQuery] parameters.
Since the format is somewhat unusual, I figured there would exist a library function that would take a complex type and generate the query string formatted text - however, I can't find one.
IOW, given a controller method defined like this:
[HttpGet("testing")]
public bool Testing([FromQuery]X x)
{
return (x?.Ys[1]?.Zs[1]?.Bs[3] == 3 && x?.Ys[1]?.Zs[0]?.A == 4);
}
And an X defined like this:
public class X
{
public Y[] Ys { get; set; }
}
public class Y
{
public Z[] Zs { get; set; }
}
public class Z
{
public int A { get; set; }
public int[] Bs { get; set; }
}
First of all, what's an example of what ASP.NET [FromQuery] is expecting to encounter in the query string in order to return true?
Secondly, is there a function somewhere that can serialize an object appropriately into whatever ASP.NET is expecting, or do I need to write one?
You can use the following "serializer"
public class QueryStringSerializer
{
private static bool IsPrimitive(object obj)
{
return obj.GetType().IsPrimitive || obj is string || obj is Guid;
}
private static bool IsEnumerable(object obj)
{
return obj is IEnumerable && !IsPrimitive(obj);
}
private static bool IsComplex(object obj)
{
return !(obj is IEnumerable) && !IsPrimitive(obj);
}
private static StringBuilder ToQueryStringInternal(object obj, string prop = null)
{
StringBuilder queryString = new StringBuilder();
//skip null values
if (obj == null)
return queryString;
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
if (IsEnumerable(obj))
{
int i = 0;
foreach (object item in obj as IEnumerable)
{
string query = ToQueryStringInternal(item, $"{prop}[{i}]").ToString();
queryString.Append(query);
i++;
}
}
else if (IsComplex(obj))
{
foreach (PropertyInfo property in properties)
{
string propName = property.Name;
object value = property.GetValue(obj);
string name = prop == null ? propName : $"{prop}.{propName}";
string query = ToQueryStringInternal(value, name).ToString();
queryString.Append(query);
}
}
else
{
string encoded = HttpUtility.UrlEncode(Convert.ToString(obj));
queryString.Append($"{prop}={encoded}&");
}
return queryString;
}
public static string ToQueryString(object obj, string propertyName = null)
{
StringBuilder queryString = ToQueryStringInternal(obj, propertyName);
queryString.Length--;
return queryString.ToString();
}
}
Usage
var x = new X
{
Ys = new Y[] {
new Y {
Zs = new Z[] { new Z { } }
},
new Y {
Zs = new Z[] {
new Z { },
new Z {
A = 1,
Bs = new int[] { 0, 1, 2, 3 }
}
}
}
}
};
string query = QueryStringSerializer.ToQueryString(x);
Result
Ys[0].Zs[0].A=0&Ys[1].Zs[0].A=0&Ys[1].Zs[1].A=1&Ys[1].Zs[1].Bs[0]=0&Ys[1].Zs[1].Bs[1]=1&Ys[1].Zs[1].Bs[2]=2&Ys[1].Zs[1].Bs[3]=3
Caution
The serializer may still contain various bags. Also, do not create array with "gaps" such as
var q = new X[] {
new X { },
null, //a gap
new X { }
};
The result will be technically correct but ASP.NET model binder will bind only the first element properly.
Related
I've got a deeply nested private fields chain which I'd like to iterate recursively to get the value of some target field.
How can this be done?
For example:
public class A
{
private B b;
public A(B b) { this.b = b; }
}
public class B
{
private C[] cItems;
public B(C[] cItems) { this.cItems = cItems; }
}
public class C
{
private string target; // <-- get this value
public C(int target) { this.target = val; }
}
public static void GetFieldValueByPath(object targetObj, string targetFieldPath)
{
// how to do it? I self-answer below
}
Usage will be:
public void DoSomething(A a)
{
var val = GetFieldValueByPath(a, "b.cItems[2].target");
}
Notes:
There is a related question about recursively getting properties, but not fields. But even then, it doesn't support array fields.
Related questions such as this one for getting fields are not recursive.
The code works for your example, but you may need to change it in case of having Dictionaries
public static object GetFieldValueByPath(object targetObj, string targetFieldPath)
{
var fieldNames = targetFieldPath.Split('.');
var type = targetObj.GetType();
foreach (var fieldName in fieldNames)
{
string name = fieldName;
int? objectIndex = default;
if (name.Contains('['))//getting fieldName without indexer
{
int indexerStart = name.IndexOf('[');
int indexerEnd = name.IndexOf(']');
objectIndex = int.Parse(name.Substring(indexerStart + 1, indexerEnd-indexerStart - 1));
name = name.Substring(0, indexerStart);
}
var field = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (objectIndex.HasValue)//here we know that field is collection
{
targetObj=((IList<object>)field.GetValue(targetObj))[0];//getting item by index
type = targetObj.GetType();
}
else
{
targetObj = field.GetValue(targetObj);
type = field.FieldType;
}
}
return targetObj;
}
OfirD's answer is on the right track, but it won't work. Not only does it not compile, but C[] does not implement IList<object>.
It also has quite a few scenarios that it does not account for. (I have not updated his code to account for these scenarios)
What if the array does not get indexed by an integer?
What if it is a jagged array?
What if the path points to properties instead of fields?
I've updated his code to work:
public static object GetFieldValueByPath(object obj, string fieldPath)
{
var flags =
BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var splitted = fieldPath.Split('.');
var current = splitted[0];
int? index = null;
// Support getting a certain index in an array field
var match = Regex.Match(current, #"\[([0-9]+)\]");
if (match.Groups.Count > 1)
{
current = fieldPath.Substring(0, match.Groups[0].Index);
index = int.Parse(match.Groups[1].Value);
}
var value = obj.GetType().GetField(current, flags).GetValue(obj);
if (value == null)
{
return null;
}
if (splitted.Length == 1)
{
return value;
}
if (index != null)
{
value = Index(value, index.Value);
}
return GetFieldValueByPath(value, string.Join(".", splitted.Skip(1)));
}
static object Index(object obj, int index)
{
var type = obj.GetType();
foreach (var property in obj.GetType().GetProperties())
{
var indexParams = property.GetIndexParameters();
if (indexParams.Length != 1) continue;
return property.GetValue(obj, new object[] { index });
}
throw new Exception($"{type} has no getter of the format {type}[int]");
}
Here's the way to do it (note the improvement over other answers, achieved using regex to prepare the path-parts ahead of time):
public static object GetFieldValueByPath(object obj, string fieldPath)
{
var flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
var parts = Regex.Matches(fieldPath, #"([^.\[]+)(?:\[(.*?)\])?").Cast<Match>().Select(match => match.Groups).ToList();
return GetFieldValueByPathParts(obj, parts, flags);
}
private static object GetFieldValueByPathParts(object obj, List<GroupCollection> parts, BindingFlags flags)
{
if (obj == null || parts.Count == 0) return obj;
var field = new Field(name: parts[0][1].Value, value: (object)null, index: parts[0][2].Value);
try
{
field.Value = obj.GetType().GetField(field.Name, flags).GetValue(obj);
}
catch (NullReferenceException ex)
{
throw new Exception($"Wrong path provided: field '{field.Name}' does not exist on '{obj}'");
}
field = TrySetEnumerableValue(field);
return GetFieldValueByPathParts(field.Value, parts.Skip(1).ToList(), flags);
}
private static Field TrySetEnumerableValue(Field field)
{
if (field.Value != null && field.Index != null)
{
var enumerable = ((IEnumerable)field.Value).Cast<object>();
field.Value = field.Index <= enumerable.Count() ? enumerable.ElementAt(field.Index.Value) : null;
}
return field;
}
Here's the definition of the Field helper class:
public class Field
{
public string Name { get; set; }
public object Value { get; set; }
public int? Index { get; set; }
public Field(string name, object value, string index)
{
Name = name;
Value = value;
Index = int.TryParse(index, out int parsed) ? parsed : (int?)null;
}
}
Usage (live demo):
public static void Main(string[] s)
{
var a1 = new A(new B(new C[] { new C(1), new C(2), new C(3) } ) );
Console.WriteLine(GetFieldValueByPath(a1, "b.cItems[2].target"));
var a2 = new A(new B(new C[] { } ) );
Console.WriteLine(GetFieldValueByPath(a2, "b.cItems[2].target"));
var a3 = new A(new B(null) );
Console.WriteLine(GetFieldValueByPath(a3, "b.cItems[2].target"));
}
The specific requirement is to replace some values in some MVC model properties if a user doesn't have specific permission.
The solution should be universal to apply to any graph of any model, and also reasonably efficient because it will be used to mask values in large lists of objects.
The assumptions are:
the graph consists of custom objects of unknown type (it's only known these will be C# classes)
all the objects of interest have public properties and only the public properties should be inspected (possibly, using custom [SensitiveData] attribute as a filter, mostly for performance reasons to ignore most of the properties)
the objects may have other child objects that need to be traversed for [SensitiveData] properties
the objects may have properties of different types of collections that need to be traversed (IEnumerables, ILists, generic IEnumerables and ILists with custom objects)
the objects may also contain collections of .NET core objects, such as IEnumerable<int>, IEnumerable<DateTime> which is of no interest to me and shouldn't be traversed
I don't want to traverse properties of .NET core objects (no need to inspect Date property of DateTime)
I don't want to iterate through .NET core objects (no need to inspect every character of string, which is IEnumerable)
in most cases it will be a tree and not a graph. Still, it would be safer to implement it for graphs with protection against getting in endless loops.
It all seems to be pretty logic and common requirements, so I thought there should be some generic, well-tested solution I could adjust to my case, just passing a callback func and maybe some filter to define properties of interest before even starting traversal.
However, so far, everything I find is limited to single Node-like type or the implementation doesn't allow to mutate properties of my choice or it goes into deep recursions with reflections and without any performance considerations.
I could implement it myself but it might turn out to be reinventing the wheel with messy recursions and reflections. Isn't there anything already existing and well-known that "just works"?
Also, I've heard that reflection SetValue and GetValue methods are slow and I should better cache setters and getters as delegates and reuse them whenever I encounter the same type again. And I will encounter the same types again because it's a ASP.NET Core web application. So it could be possible to gain noticeable performance boost over naive reflection solutions if I cache every setter/getter of interest for future reuse.
It took some struggling but with awesome graph traversal example from Eric Lippert and FastMember library I have something that works:
[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;
}
}
For testing:
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);
}
}
It replaces marked strings, no matter how deep inside POCOs they are.
Also I can use the walker for every other property access / mutation case I can imagine.
Still not sure if I reinvented a wheel here...
I'm trying to loop through a DetailClass objects inside a List using reflection just like for string fields, but I can't figure out how.
class DetailClass
{
public string FieldDetail1 { get; set; }
public string FieldDetail2 { get; set; }
public string FieldDetail3 { get; set; }
}
class SomeClass
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
public List<DetailClass> Artikli { get; set; }
}
private static PropertyInfo[] GetProperties(object obj)
{
return obj.GetType().GetProperties();
}
var myData = new SomeClass();
var prop = GetProperties(myData);
foreach (var item in prop)
{
if (item.PropertyType == typeof(string))
{
var name = item.Name,
var value = item.GetValue(myData).ToString()));
}
//how to get name and value for data inside List<DetailClass>?
}
You were trying to enumerate properties of the parent class
GetValue needs the a reference to the class you are dealing with
Code
var myData = new SomeClass();
myData.Artikli = new List<DetailClass>() { new DetailClass() { FieldDetail1 = "asd", FieldDetail2 = "sdfd", FieldDetail3 = "sdfsg" } };
foreach (var obj in myData.Artikli)
{
foreach (var item in obj.GetType().GetProperties())
{
if (item.PropertyType == typeof(string))
{
var name = item.Name;
var val = item.GetValue(obj);
Console.WriteLine(name + ", " + val);
}
}
}
Demo Here
Additional Resources
PropertyInfo.GetValue Method (Object)
Returns the property value of a specified object.
Parameters
obj
Type: System.Object
The object whose property value will be returned.
You can use your method recursively to get inside all layer of properties
You can check if
item.PropertyType.GetInterfaces().Contains(typeof(IEnumerable))
and if true cast (IEnumerable)item.GetValue(myData) and iterate on the result
recursively.
Just like TheDude answered, you can use a recursive method like so;
private void Recursion(object obj)
{
var props = GetProperties(obj);
foreach (var item in props)
{
if (item.PropertyType == typeof(string))
{
var name = item.Name;
var value = item.GetValue(obj)?.ToString();
}
else if (item.PropertyType == typeof(List<DetailClass>))
{
var test = (List<DetailClass>) item.GetValue(obj);
foreach (var t in test)
{
Recursion(t);
}
}
}
}
And do whatever you want with the name and values in the list.
I have a class, which is created and populated from an xml string, I've simplified it for example purposes:
[XmlRoot("Person")]
public sealed class Person
{
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Location")]
public string Location { get; set; }
[XmlElement("Emails", Type = typeof(PersonEmails)]
public PersonEmails Emails { get; set; }
}
public class PersonEmails
{
[XmlElement("Email", Type = typeof(PersonEmail))]
public PersonEmail[] Emails { get; set; }
}
public class PersonEmail
{
[XmlAttribute("Type")]
public string Type { get; set; }
[XmlText]
public string Value { get; set; }
}
To extract the information, I'm trying to load them into another class, which is simply:
public class TransferObject
{
public string Name { get; set; }
public ObjectField[] Fields { get; set; }
}
public class ObjectField
{
public string Name { get; set; }
public string Value { get; set; }
}
I'm only populating "Fields" from the other object, which would simply be (Name = "Location", Value = "London"), but for Emails, (Name = "Email"+Type, Value = jeff#here.com)
Currently I can populate all the other fields, but I'm stuck with Emails, and knowing how to dig deep enough to be able to use reflection (or not) to get the information I need. Currently I'm using:
Person person = Person.FromXmlString(xmlString);
List<ObjectField> fields = new List<ObjectField>();
foreach (PropertyInfo pinfo in person.getType().GetProperties()
{
fields.Add(new ObjectField { Name = pinfo.Name, Value = pinfo.getValue(person, null).ToString();
}
How can I expand on the above to add all my emails to the list?
You are trying to type cast a complex values type to string value so you lost the data. Instead use following code:
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Name = "Person One";
person.Location = "India";
person.Emails = new PersonEmails();
person.Phones = new PersonPhones();
person.Emails.Emails = new PersonEmail[] { new PersonEmail() { Type = "Official", Value = "xyz#official.com" }, new PersonEmail() { Type = "Personal", Value = "xyz#personal.com" } };
person.Phones.Phones = new PersonPhone[] { new PersonPhone() { Type = "Official", Value = "789-456-1230" }, new PersonPhone() { Type = "Personal", Value = "123-456-7890" } };
List<ObjectField> fields = new List<ObjectField>();
fields = GetPropertyValues(person);
}
static List<ObjectField> GetPropertyValues(object obj)
{
List<ObjectField> propList = new List<ObjectField>();
foreach (PropertyInfo pinfo in obj.GetType().GetProperties())
{
var value = pinfo.GetValue(obj, null);
if (pinfo.PropertyType.IsArray)
{
var arr = value as object[];
for (var i = 0; i < arr.Length; i++)
{
if (arr[i].GetType().IsPrimitive)
{
propList.Add(new ObjectField() { Name = pinfo.Name + i.ToString(), Value = arr[i].ToString() });
}
else
{
var lst = GetPropertyValues(arr[i]);
if (lst != null && lst.Count > 0)
propList.AddRange(lst);
}
}
}
else
{
if (pinfo.PropertyType.IsPrimitive || value.GetType() == typeof(string))
{
propList.Add(new ObjectField() { Name = pinfo.Name, Value = value.ToString() });
}
else
{
var lst = GetPropertyValues(value);
if (lst != null && lst.Count > 0)
propList.AddRange(lst);
}
}
}
return propList;
}
}
Check this snippet out:
if(pinfo.PropertyType.IsArray)
{
// Grab the actual instance of the array.
// We'll have to use it in a few spots.
var array = pinfo.GetValue(personObject);
// Get the length of the array and build an indexArray.
int length = (int)pinfo.PropertyType.GetProperty("Length").GetValue(array);
// Get the "GetValue" method so we can extact the array values
var getValue = findGetValue(pinfo.PropertyType);
// Cycle through each index and use our "getValue" to fetch the value from the array.
for(int i=0; i<length; i++)
fields.Add(new ObjectField { Name = pinfo.Name, Value = getValue.Invoke(array, new object[]{i}).ToString();
}
// Looks for the "GetValue(int index)" MethodInfo.
private static System.Reflection.MethodInfo findGetValue(Type t)
{
return (from mi in t.GetMethods()
where mi.Name == "GetValue"
let parms = mi.GetParameters()
where parms.Length == 1
from p in parms
where p.ParameterType == typeof(int)
select mi).First();
}
You can definately do it with Reflection... You can take advantage of the fact that a Type can tell you if it's an array or not (IsArray)... and then take advantage of the fact that an Array has a method GetValue(int index) that will give you a value back.
Per your comment
Because Emails is a property within a different class, recursion should be used. However the trick is knowing when to go to the next level. Really that is up to you, but
if it were me, I would use some sort of Attribute:
static void fetchProperties(Object instance, List<ObjectField> fields)
{
foreach(var pinfo in instance.GetType().GetProperties())
{
if(pinfo.PropertyType.IsArray)
{
... // Code described above
}
else if(pinfo.PropertyType.GetCustomAttributes(typeof(SomeAttribute), false).Any())
// Go the next level
fetchProperties(pinfo.GetValue(instance), fields);
else
{
... // Do normal code
}
}
}
I have run into a situation where I need to compare two different lists to each other and I am wondering what the best method is for doing this? I thought something like this would work but it doesn't and I can't figure out why. The Linq query is returning records it shouldn't. This is my first run at trying to figure something like this out so it is undoubtedly messy.
private static List<ColumnDefinition> FindTableStructureUpdates(List<ColumnDefinition> colDefs, List<ColumnDefinition> tblCols)
{
List<ColumnDefinition> ColsToUpdate = new List<ColumnDefinition>();
for (int i = 0; i < colDefs.Count; ++i)
{
string colDefName = colDefs[i].ColName;
string colDefDataType = colDefs[i].ColType;
string colDefAttribute = colDefs[i].ColAttributes;
var query = from tbl in tblCols
where tbl.ColName != colDefName && tbl.ColType != colDefDataType && tbl.ColAttributes != colDefAttribute
select new { colDefName, colDefDataType, colDefAttribute };
if (query.Count() > 0)
{
foreach (var item in query)
{
ColsToUpdate.Add(new ColumnDefinition(item.colDefName, item.colDefDataType, item.colDefAttribute));
}
}
}
return ColsToUpdate;
Any suggestions would be great.
Thanks.
IEquatable Implementation??
#region IEquatable<ColumnDefinition> Members
public bool Equals(ColumnDefinition other)
{
if (this.ColName.Equals(other.ColName) && this.ColType.Equals(other.ColType) && this.ColAttributes.Equals(other.ColAttributes))
return true;
return false;
}
Can't you use Enumerable.Except ?
public static IEnumerable<TSource> Except<TSource>(
this IEnumerable<TSource> first,
IEnumerable<TSource> second
)
More details.
An example tested in Snippet Compiler
using System;
using System.Linq;
using System.Collections.Generic;
class ColumnDefinition : IEquatable<ColumnDefinition>
{
public string Name { get; set; }
public string Type { get; set; }
public string Attr { get; set; }
public ColumnDefinition()
{
Name = string.Empty;
Type = string.Empty;
Attr = string.Empty;
}
public bool Equals(ColumnDefinition other)
{
return Name.Equals(other.Name) && Type.Equals(other.Type) && Attr.Equals(other.Attr);
}
public override bool Equals(object value)
{
return (value is ColumnDefinition) ? Equals(value as ColumnDefinition) : false;
}
public override int GetHashCode()
{
return Name.GetHashCode() ^ Type.GetHashCode() ^ Attr.GetHashCode();
}
public override string ToString()
{
return string.Concat("{", Name, ":", Type, ":", Attr, "}");
}
}
public class Program
{
public static void Main(string[] args)
{
try
{
MyMain(args);
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
Console.ReadKey();
}
}
public static void MyMain(string[] args)
{
var list1 = new []
{
new ColumnDefinition { Name = "foo", Type = "int", Attr = "0" },
new ColumnDefinition { Name = "bar", Type = "int", Attr = "1" },
};
var list2 = new []
{
new ColumnDefinition { Name = "foo", Type = "int", Attr = "0" },
new ColumnDefinition { Name = "bar", Type = "string", Attr = "1" },
};
foreach (var changed in Enumerable.Except(list1, list2))
{
Console.WriteLine(changed);
}
}
}