.Net reflection to get nested property with custom attribute - c#

I want to know the best way to get the prop info and value using reflection for a nested class by its custom attribute name.
With below code I can get the prop info via recursion. But is there a better way or using LINQ. Note that I do not want to hard code the class type as similar to other solution
I also want to get the property value by custom attribute
e.g var propValue = ?????
public class PlanetRoot
{
public void GetNeighborMoon()
{
Planet planet = new Planet();
Product product = new Product();
Neighbor neighbor = new Neighbor();
neighbor.Moons = 10;
neighbor.RingColor = "Red";
product.Neighbors = new List<Neighbor>();
product.Neighbors.Add(neighbor);
planet.Product = product;
//1. Get the RingColor property info of neighbor with attribute MyDBField(Name = "NeighborRing") . Is there a better way
PropertyInfo propInfo = null;
DoRecursiveGetProperty(planet.GetType(), "NeighborRing", out propInfo );
//2. Get the RingColor property value of neighbor with attribute MyDBField(Name = "NeighborRing")
//var propValue = GetPropertyValue(????);
}
}
private static PropertyInfo DoRecursiveGetProperty(Type type, string attribName, out PropertyInfo propInfo)
{
PropertyInfo[] pi = type.GetProperties();
propInfo= null;
foreach (PropertyInfo p in pi)
{
var dbFieldAttribute = (MyDBFieldAttribute)Attribute.GetCustomAttribute(p, typeof(MyDBFieldAttribute));
if (dbFieldAttribute != null && attribName.ToUpper() == dbFieldAttribute.Name.ToUpper())
{
propInfo= p;
//Console.WriteLine(p.Name + " : " + (dbFieldAttribute != null && dbFieldAttribute.Name != null ? dbFieldAttribute.Name : "****"));
return true;
}
if (p.PropertyType.IsClass && !p.PropertyType.IsValueType && !p.PropertyType.IsPrimitive
&& p.PropertyType.FullName != "System.String")
if (propInfo != null) return true;
else DoRecursiveGetProperty(p.PropertyType, attribName, out propInfo);
}
return false;
}
public class Planet
{
public string PlanetId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Product Product { get; set; }
[MyDBField(Name="PubDate")]
public string Publishdate { get; set; }
}
public class Product
{
public string ProductId { get; set; }
public List<Neighbor> Neighbors { get; set; }
}
public class Neighbor
{
[MyDBField(Name = "NeighborRing")]
public string RingColor { get; set; }
public int Moons { get; set; }
}
public class MyDBFieldAttribute : System.Attribute
{
public string Name { get; set; }
}

In order to get the value for a nested member that might be in a collection, you need to iterate the collections, and track the current object.
Assuming you don't need the resulting PropInfo for other reasons, just try to get the value:
private static bool TryRecursiveGetValueWithMyDBFieldName(object startObject, string attribName, out object propValue) {
PropertyInfo[] pi = startObject.GetType().GetProperties();
foreach (PropertyInfo p in pi) {
var dbFieldAttribute = (MyDBFieldAttribute)Attribute.GetCustomAttribute(p, typeof(MyDBFieldAttribute));
if (dbFieldAttribute != null && dbFieldAttribute.Name.Equals(attribName, StringComparison.CurrentCultureIgnoreCase)) {
//Console.WriteLine(p.Name + " : " + (dbFieldAttribute != null && dbFieldAttribute.Name != null ? dbFieldAttribute.Name : "****"));
propValue = p.GetValue(startObject);
return true;
}
if (p.PropertyType.IsClass && !p.PropertyType.IsValueType && !p.PropertyType.IsPrimitive &&
!p.PropertyType.FullName.StartsWith("System.")) {
var tryObject = p.GetValue(startObject);
if (tryObject != null && TryRecursiveGetValueWithMyDBFieldName(tryObject, attribName, out propValue))
return true;
}
if (p.PropertyType.IsClass && p.GetValue(startObject) is IEnumerable ip) {
foreach (var obj in ip) {
if (obj != null && TryRecursiveGetValueWithMyDBFieldName(obj, attribName, out propValue))
return true;
}
}
}
propValue = default;
return false;
}
To use it, call with the initial object:
var foundAttrib = TryRecursiveGetValueWithMyDBFieldName(planet, "NeighborRing", out var propValue);
NOTE: This will return the value of the first object with a matching attribute, as e.g. every member of the List<Neighbor> member will have the MyDBField attribute with the Name property of NeighborRing.

Related

How can I get the value of specific key inside a model dynamically?

I have here my model:
public class RoleModel
{
public int ID { get; set; }
public string RoleName { get; set; }
public UserRoleModel TrackAndTrace { get; set; }
public UserRoleModel MailDelivery { get; set; }
public UserRoleModel MailAccounting { get; set; }
public UserRoleModel PerformanceMonitoring { get; set; }
public UserRoleModel AdminSettings { get; set; }
}
I want to get the value of any key of 'RoleModel'
public bool SaveRoleModule(RoleModel role)
{
PropertyInfo[] properties = typeof(RoleModel).GetProperties();
foreach (PropertyInfo property in properties)
{
if(property.Name != "ID" && property.Name != "RoleName")
{
Console.WriteLine(role[property.Name]);//(this doesn't work) I want it dynamic
Console.WriteLine(role.TrackAndTrace); //not like this
}
}
return true;
}
I used loop to shorten my code.
You can use this method here: Get property value from string using reflection in C# like this:
public bool SaveRoleModule(RoleModel role)
{
PropertyInfo[] properties = typeof(RoleModel).GetProperties();
foreach (PropertyInfo property in properties)
{
if(property.Name != "ID" && property.Name != "RoleName")
{
var value = GetPropValue(role, property.Name);
Console.WriteLine(value);//(this doesn't work) I want it dynamic
Console.WriteLine(role.TrackAndTrace); //not like this
}
}
return true;
}
public static object GetPropValue(object src, string propName)
{
return src.GetType().GetProperty(propName).GetValue(src, null);
}
You could try something like this:
public bool SaveRoleModule(RoleModel role)
{
PropertyInfo[] properties = typeof(RoleModel).GetProperties();
foreach (PropertyInfo property in properties)
{
if(property.Name != "ID" && property.Name != "RoleName")
{
Type t = role.GetType();
PropertyInfo p = t.GetProperty(property.Name);
UserRoleModel urm = ((UserRoleModel)p.GetValue(role, null));
// do something with urm
}
}
return true;
}
Though Fabio is right, this does seem strange and potentially based on faulty reasoning.

Get complex type properties from PropertyInfo

I trying to build a generic recursive function to iterate all properties / complex properties and return an array of all properties from the following structre:
public class Root
{
[FieldCodeItems(1, EnumFieldCode.INT, "ED", "0204")]
public int Prop1 { get; set; }
public Child Child { get; set; }
}
public class Child
{
[FieldCodeItems(1, EnumFieldCode.INT, "ED", "0208")]
public int PropChild1 { get; set; }
[FieldCodeItems(19, EnumFieldCode.ALPHANUMERIC, "ED", "0208")]
public string PropChild2 { get; set; }
public Child1 Child1 { get; set; }
}
public class Child1
{
[FieldCodeItems(1, EnumFieldCode.INT, "ED", "0211")]
public int PropChild3 { get; set; }
}
public class MyReturClass
{
public string FileCode { get; set; }
public string FieldCode { get; set; }
}
I can read all properties from Root class but I can't get the properties from the complex properties:
public static List<MyReturClass> GetItems<T>(T obj)
{
var ret = new List<MyReturClass>();
PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo property in properties)
{
IEnumerable<Attribute> attributes = property.GetCustomAttributes();
foreach (Attribute attribute in attributes)
{
//here I read values from a custom property
var tr = (FieldCodeItems)attribute;
var value = obj.GetType().GetProperty(property.Name).GetValue(obj, null);
if (value == null) continue;
ret.Add(new MyReturClass
{
FieldCode = tr.FieldCode,
FileCode = tr.FileCode
});
}
//If is complex object (Child, Child1 etc)
if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
{
//I would like pass the complex property as parameter, but at this moment
//the property variable is a PropertyInfo and I need the complex type
//to get properties values from this object
GetItems(property); //Error. Also tried with GetItems(property.PropertyType);
}
}
return ret;
}
I would like pass the complex property as parameter, but at this moment the property variable is a PropertyInfo and I need the complex type to get properties values from this object.
How can I get this object?
I searched here, here and here but the solutions don't solve my problem.
Edit - 03-08-2018
Finally I found the awswer here
I added this code to solve the problem:
//If is complex object (Child, Child1 etc)
if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
{
if (obj == null) { return null; }
Type type = obj.GetType();
PropertyInfo info = type.GetProperty(property.Name);
if (info == null) { return null; }
var v = info.GetValue(obj, null);
if (v != null)
GetItems(v);
}
have a look at https://learn.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/how-to-examine-and-instantiate-generic-types-with-reflection
I am not sure if what you are looking for is going to work because in you EF you are configuring the classes as ComplexType. I am not sure the class itself knows its a complexType. You might end up creating a method on the DBcontext...

Get value/obj ref in generic code by prop name

I recently created generic sql command generating code. Everything works except one thing i currently struggle with.
How do i get the instance/the value of the Answer prop by the given property name?
What i have:
Question instance as object (for example myObject)
property of question + inner prop name (string) + inner class type
[Table("Question")]
public class Question
{
[Column("QuestionID", true)]
public int QuestionId { get; set; }
[Column("Number")]
public string Nr { get; set; }
[Column("CorrectAnswer", typeof(Answer), nameof(Answer.Text))]
public Answer CorrectAnswer { get; set; }
}
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class ColumnAttribute : DatabaseAttribute
{
public bool IsTechnicalId { get; private set; } = false;
public string DeepPropName { get; private set; } = null;
public Type DeepClass { get; private set; } = null;
public bool HasDeepProp { get; private set; } = false;
public ColumnAttribute(string name) : base(name)
{
}
/// <summary>
/// Creates a deep object property column definition
/// </summary>
/// <param name="name"></param>
/// <param name="deepProperty"></param>
public ColumnAttribute(string name, Type deepClass, string deepPropertyName) : base(name)
{
DeepPropName = deepPropertyName;
DeepClass = deepClass;
HasDeepProp = true;
}
public ColumnAttribute(string name, bool isTechnicalId) : this(name)
{
IsTechnicalId = isTechnicalId;
}
}
My current code for getting the props and the values looks like this.
public static IEnumerable<DatabaseAttributeContainer> GetAllAttributes<TClass>(TClass obj, bool inherit = false)
{
List<DatabaseAttributeContainer> propValues = new List<DatabaseAttributeContainer>();
object value = null;
Type classType = typeof(TClass);
PropertyInfo[] pInfos = inherit ? classType.GetProperties() : classType.GetProperties(_NonInheritedFlag);
DatabaseAttribute[] attributes = null;
DatabaseAttribute attr = null;
ColumnAttribute colAttr = null;
PropertyInfo[] pInfosDeep = null;
foreach (PropertyInfo pInfo in pInfos)
{
attributes = (DatabaseAttribute[])pInfo.GetCustomAttributes(typeof(DatabaseAttribute));
attr = attributes.FirstOrDefault();
if(!(attr is IgnoreAttribute))
{
colAttr = (ColumnAttribute)attr;
//broken
if(colAttr.HasDeepProp)
{
pInfosDeep = colAttr.DeepClass.GetProperties(_NonInheritedFlag);
foreach (PropertyInfo pInfoDeep in pInfosDeep)
{
if(pInfoDeep.Name == colAttr.DeepPropName)
{
//Need object instance of Answer for "GetValue()"
//value = pInfoDeep.GetValue(obj, null);
break;
}
}
}
//broken end
else
{
if (obj != null)
value = pInfo.GetValue(obj, null);
propValues.Add(new DatabaseAttributeContainer(attr, value));
}
}
}
propValues.Add(GetAttributeByClass<TClass>());
if (propValues.Count == 0)
throw new ApplicationException(typeof(TClass) + "has no DatabaseAttribute attribute");
return propValues;
}
Everything works perfectly with the only exception: How to get the property value of another object inside the object im searching for?
Am i able to get any instance of a property of an instanced object?
Edit1:
I could get the ref by
if(pInfo.Name == "CorrectAnswer")
answerRef = pInfo.GetValue(obj, null);
and than get the props from the answerRef castet to answer to get the value
for my given propName
Solution
public static IEnumerable<DatabaseAttributeContainer> GetAllAttributes<TClass>(TClass obj, bool inherit = false)
{
List<DatabaseAttributeContainer> propValues = new List<DatabaseAttributeContainer>();
object value = null;
Type classType = typeof(TClass);
PropertyInfo[] pInfos = inherit ? classType.GetProperties() : classType.GetProperties(_NonInheritedFlag);
DatabaseAttribute[] attributes = null;
DatabaseAttribute attr = null;
ColumnAttribute colAttr = null;
PropertyInfo pInfoDeep = null;
object deepRef = null;
//ToDo: get custom attributes from this obj and iterate over it with prop name
//get propInfo (performance increase)
foreach (PropertyInfo pInfo in pInfos)
{
attributes = (DatabaseAttribute[])pInfo.GetCustomAttributes(typeof(DatabaseAttribute));
attr = attributes.FirstOrDefault();
if (!(attr is IgnoreAttribute))
{
colAttr = attr as ColumnAttribute;
if (colAttr != null && colAttr.HasDeepProp && obj != null)
{
//get ref value
deepRef = pInfo.GetValue(obj, null);
deepRef = Convert.ChangeType(deepRef, colAttr.DeepType);
pInfoDeep = deepRef.GetType().GetProperty(colAttr.DeepPropName);
if (pInfoDeep == null)
throw new ApplicationException("Property with name " + colAttr.DeepPropName + " not found");
value = pInfoDeep.GetValue(deepRef, null);
}
else
{
if (obj != null)
value = pInfo.GetValue(obj, null);
}
propValues.Add(new DatabaseAttributeContainer(attr, value));
}
}
propValues.Add(GetAttributeByClass<TClass>());
if (propValues.Count == 0)
throw new ApplicationException(typeof(TClass) + "has no DatabaseAttribute attribute");
return propValues;
}

Converting Flat file to List which is a property of another class

public class DummyResponse
{
public int UpdatedRecords { get; set; }
public string Id { get; set; }
public bool Isvalid { get; set; }
}
public class Request
{
public List<DummyResponse> Changes { get; set; }
public string ReuestedBy { get; set; }
public Request()
{
Changes = new List<DummyResponse>();
}
}
I have a flat file which contains tab separated data for Dummy Response.
I want this to be serialized to Request object.
The implementation needs should be generic as in I only need user to pass T (Request) in this case and identify the correct sub type to be filled from flat file.
I have below Code to convert it into Object. How ever its only working for properties having string type.
interface ICollectionBuilder
{
object Build(IList dictionaries);
}
internal class CollectionBuilder<T> : ICollectionBuilder where T : new()
{
public object Build(IList dictionaries)
{
var dictConverter = new DictionaryConerter<T>();
return dictionaries
.OfType<IDictionary<string, object>>()
.Select(dict => dictConverter.ConvertTyped(dict))
.ToList();
}
}
interface IDictionaryConverter
{
object Convert(IDictionary<string, object> dict);
}
internal class DictionaryConerter<T> : IDictionaryConverter where T : new()
{
public object Convert(IDictionary<string, object> dict)
{
return ConvertTyped(dict);
}
public T ConvertTyped(IDictionary<string, object> dict)
{
T t = new T();
var properties = t.GetType().GetProperties();
foreach (KeyValuePair<string, object> curr in dict)
{
if (String.IsNullOrEmpty(curr.Key)) continue;
if (curr.Value == null) continue;
Type valType = null;
Type newType = null;
PropertyInfo currProperty = null;
foreach (PropertyInfo p in properties)
{
if (String.IsNullOrEmpty(p.Name)) continue;
if (String.Compare(p.Name.ToLower(), curr.Key.ToLower()) == 0)
{
valType = t.GetType().GetProperty(p.Name).PropertyType;
newType = Nullable.GetUnderlyingType(valType) ?? valType;
currProperty = p;
break;
}
}
object newVal = curr.Value;
var curDict = curr.Value as IDictionary<string, object>;
var curList = curr.Value as IList;
if (curDict != null && newType.GetConstructor(Type.EmptyTypes) != null)
{
newVal = ((IDictionaryConverter)Activator.CreateInstance(typeof(DictionaryConerter<>).MakeGenericType(newType))).Convert(curDict);
}
else if (
curList != null &&
curList.OfType<IDictionary<string, object>>().Any() &&
newType.IsGenericType &&
newType.GetGenericTypeDefinition() == typeof(List<>) &&
newType.GetGenericArguments()[0].GetConstructor(Type.EmptyTypes) != null)
{
newVal = ((ICollectionBuilder)Activator.CreateInstance(typeof(CollectionBuilder<>).MakeGenericType(newType.GetGenericArguments()[0]))).Build(curList);
}
t.GetType().GetProperty(currProperty.Name).SetValue(t, newVal);
}
return t;
}
}
Example usage:
void Main ()
{
var dict = new Dictionary<string,object>();
dict.Add("ReuestedBy",abc);
var innerDict = new Dictionary<string,object>();
var list = new LIst<Dictionary<string,object>>();
innerDict.Add("UpdatedRecords","45");
innerDict.Add("Id","1");
innerDict.Add("IsValid","False");
dict.Add("Changes",list )
}
The problem here is it not working for any other type other than string.
I fixed it using below code while setting value
propertyVal = Convert.ChangeType(propertyVal, targetType);
propertyInfo.SetValue(inputObject, propertyVal, null);

Comparing (specific) object properties in c#

Building upon this answer for comparing objects in C#
Comparing object properties in c#
Knowing this is a complex topic, I wanted to handle a few more specific structures.
While this code will match properties if they are simple value types such as this object:
public class BasicStuff
{
public int anInt { get; set; }
public bool aBool { get; set; }
}
But as soon as it gets any more complex, this code fails.
So what I would like to do is make it a bit more usable for nested objects of the above, such as:
public class NestedStuff
{
public BasicStuff theBasic { get; set; }
}
Any array of the above, such as:
public class ArrayStuff
{
public BasicStuff[] theBasicArray { get; set; }
}
And finally any combination of the above:
public class AllTheStuff
{
public int anInt { get; set; }
public bool aBool { get; set; }
public BasicStuff theBasic { get; set; }
public BasicStuff[] theBasicArray { get; set; }
}
So what I came up with was the following:
public static bool AllPublicPropertiesEqual<T>(T AObj, T BObj, params string[] ignore) where T : class
{
if (AObj != null && BObj != null)
{
Type type = typeof(T);
List<string> ignoreList = new List<string>(ignore);
foreach (PropertyInfo pInfo in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!ignoreList.Contains(pInfo.Name))
{
if (pInfo.PropertyType.IsArray)
{
object AValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
object BValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);
string t = AValue.GetType().ToString();
if (!AllPublicPropertiesEqual<object>(AValue, BValue))
return false;
}
else if (!pInfo.PropertyType.IsValueType && !pInfo.PropertyType.IsPrimitive && !pInfo.PropertyType.IsEnum && pInfo.PropertyType != typeof(string))
//else if (Convert.GetTypeCode(pInfo.PropertyType) == TypeCode.Object)
{
object AValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
object BValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);
if (!AllPublicPropertiesEqual<object>(BValue, AValue))
return false;
}
else
{
object selfValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
object toValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);
if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
return false;
}
}
}
return true;
}
return AObj == BObj;
}
Only this fails because when recursively calling AllPublicPropertiesEqual, I need to pass the specific values type rather than just a generic object type.
But I dont know how to do this.... Any ideas are greatly appreciated...
Your method does not have to be generic. You can change the beginning of the method to:
public static bool AllPublicPropertiesEqual(object AObj, object BObj, params string[] ignore)
{
if (AObj != null && BObj != null)
{
Type type = AObj.GetType();
if (BObj.GetType() != type)
throw new Exception("Objects should be of the same type");
....
}
....
}
Well first thing what is that you should create Interface for those classes because from what I see you can combine those classes but all of them will have the same properties. Second option is create some Base abstract class with those properties and inherit from it. It is up to you what you choose.
Than create in this Base class or Interface Equals functions where you will equal directly the shared properties it is much easier and I think more efficient :-)
public abstract class BaseStaff {
public int anInt { get; set; }
public bool aBool { get; set; }
public abstract bool Equals(BaseStaff anotherStaff);
}
Then you can use this class and create your BasicStaff class and AllTheStaff class too in BasicStaff implement Equals method like this.
public override bool Equals(BaseStaff staff) {
this.anInt == staff.anInt &&
this.aBool == staff.aBool;
}
In AllTheStaff u can than override this method like this
public override bool Equals(BaseStaff staff) {
bool baseEquals = base.Equals(staff);
bool basicStaffEquals = this.BasicStaff.Equals(staff);
return baseEquals || basicStaffEquals;
}
This is just core idea and maybe I dont understand you well what you really want to achieve but hope it helps you :)
tested Solution
public static bool AllPublicPropertiesEqual<T>(T AObj, T BObj, params string[] ignore) where T : class
{
if (AObj != null && BObj != null)
{
Type type = typeof(T);
List<string> ignoreList = new List<string>(ignore);
foreach (PropertyInfo pInfo in type.GetCType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!ignoreList.Contains(pInfo.Name))
{
if (pInfo.PropertyType.IsArray)
{
object aElementValues = (type.GetProperty(pInfo.Name).GetValue(AObj, null));
object bElementValues = (type.GetProperty(pInfo.Name).GetValue(BObj, null));
if (aElementValues != null && bElementValues != null)
{
List<object> AListValues = new List<object>();
List<object> BListValues = new List<object>();
foreach (var v in (IEnumerable)aElementValues)
AListValues.Add(v);
foreach (var v in (IEnumerable)bElementValues)
BListValues.Add(v);
if (AListValues.Count == BListValues.Count)
{
object[] aArray = AListValues.ToArray();
object[] bArray = BListValues.ToArray();
for (int i = 0; i < aArray.Length; i++)
{
if (!AllPublicPropertiesEqual(aArray[i], bArray[i]))
return false;
}
}
else
return false;
}
else if ((aElementValues == null) != (bElementValues == null))
return false;
}
else if (!pInfo.PropertyType.IsValueType && !pInfo.PropertyType.IsPrimitive && !pInfo.PropertyType.IsEnum && pInfo.PropertyType != typeof(string))
//else if (Convert.GetTypeCode(pInfo.PropertyType) == TypeCode.Object)
{
object AObjectValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
object BObjectValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);
if (!AllPublicPropertiesEqual(BObjectValue, AObjectValue))
return false;
}
else
{
object aValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
object bValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);
if (aValue != bValue && (aValue == null || !aValue.Equals(bValue)))
return false;
}
}
}
return true;
}
return AObj == BObj;
}
}

Categories

Resources