How to list class properties that are classes too?

I'm trying to list a class's properties in a drop down menu using reflection. It works great but if the class has another class as a property, that's where I'm having problems.

I'm binding to a drop down like this:
            foreach (PropertyInfo property in typeof(Product).GetProperties())
                ddlClassColumns.Items.Add(property.Name + " (" +
                        property.PropertyType.Name.ToLower() + ")", property.Name));

Which results in this using the classes below. Dropdown:
text: ProductID (string),    value: ProductID
text: Name (string),           value: Name
text: MetaData (MetaData),    value: MetaData

The last one is where I'm having trouble. What I'm trying to do is flatten it out to dot notation like this:

text: ProductID (string),    value: ProductID
text: Name (string),           value: Name
text: MetaData.Title (string),    value: MetaData.Title
text: MetaData.Keywords (string),    value: MetaData.Keywords
text: MetaData.Description (string),    value: MetaData.Description

How can I do this?
public class Product
    {
        private string _productID = String.Empty;
        public string ProductID
        {
            get { return _productID; }
            set { _productID = value; }
        }
 
        private string _name = String.Empty;
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
 
        private MetaData _metaData = new MetaData();
        public MetaData MetaData
        {
            get { return _metaData; }
            set { _metaData = value; }
        }
    }
 
    public class MetaData
    {
        private string _title = String.Empty;
        public string Title
        {
            get { return _title; }
            set { _title = value; }
        }
 
        private string _keywords = String.Empty;
        public string Keywords
        {
            get { return _keywords; }
            set { _keywords = value; }
        }
 
        private string _description = String.Empty;
        public string Description
        {
            get { return _description; }
            set { _description = value; }
        }
    }

Open in new window

bemara57Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

drichardsCommented:
If you need it to work generically for any properties of any class, then you are pretty much out of luck because you'll have a hard time determining which things to expand.  In the example you give, the string properties would get expanded also since string is a class with properties.  You'd end up with tons of code trying to decide what to expand.

If you just need it to work specifically for MetaData, then you just put a case in the loop and if the property name is MetaData, start another loop to add its properties to the list in the form you want.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
bemara57Author Commented:
Thanks, but I'm having a hard time assigning a value to a property of a sub class. Say I'm trying to dynamically assign a value to Product.MetaData.Keywords, how do I do this? I tried the code below but I get an exception:

string propertyToAssign = "MetaData.Keywords";
Product oProduct = new Product();

PropertyInfo propertyParent = oProduct .GetType().GetProperty(propertyToAssign.Split('.')[0]); //MetaData
PropertyInfo propertyInfo = propertyParent.PropertyType.GetProperty(propertyToAssign.Split('.')[1]); //Keywords

propertyInfo.SetValue(propertyParent, 13.54, null);

And the exception is:
System.Reflection.TargetException: Object does not match target type. at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture) at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
0
bemara57Author Commented:
I got it working. Just wanted to share my end result, which is an extension to convert a DataTable to a collection of a classes (strong-typed). It does this by accepting a hashtable that maps the columns of the DataTable to the property names of the class. Pls share any comments on this if any:
        public static List<T> ToClassCollection<T>(this DataTable param, Hashtable mappings) where T : new()
        {
            List<T> col = new List<T>();
 
            foreach (DataRow row in param.Rows)
            {
                if (row.HasErrors)
                    break;
 
                T objInstance = new T();
 
                foreach (DictionaryEntry map in mappings)
                {
                    if (row[map.Value.ToString()] != DBNull.Value)
                    { 
                        object tableCell = row[map.Value.ToString()];
                        
                        try
                        {
                            if (map.Key.ToString().Contains('.'))
                            {
                                string[] propertySplit = map.Key.ToString().Split('.');
 
                                // Get instance of subclass
                                PropertyInfo subClassInfo = objInstance.GetType().GetProperty(propertySplit[0]);
                                object subClassInstance = Activator.CreateInstance(subClassInfo.PropertyType);
 
                                // Get property of subclass
                                PropertyInfo propertyInfo = subClassInstance.GetType().
                                    GetProperty(propertySplit[1]);
 
                                if (propertyInfo != null)
                                {
                                    if (propertyInfo.PropertyType == typeof(string))
                                        propertyInfo.SetValue(subClassInstance, tableCell.ToString(), null);
                                    else if (propertyInfo.PropertyType == typeof(bool))
                                        propertyInfo.SetValue(subClassInstance, Convert.ToBoolean(tableCell), null);
                                    else if (propertyInfo.PropertyType == typeof(int))
                                        propertyInfo.SetValue(subClassInstance, Convert.ToInt32(tableCell), null);
                                    else if (propertyInfo.PropertyType == typeof(decimal))
                                        propertyInfo.SetValue(subClassInstance, Convert.ToDecimal(tableCell), null);
                                    else if (propertyInfo.PropertyType == typeof(double))
                                        propertyInfo.SetValue(subClassInstance, Convert.ToDouble(tableCell), null);
                                    else if (propertyInfo.PropertyType == typeof(DateTime))
                                        propertyInfo.SetValue(subClassInstance, Convert.ToDateTime(tableCell), null);
                                }
 
                                subClassInfo.SetValue(objInstance, subClassInstance, null);
                            }
                            else
                            {
                                PropertyInfo propertyInfo = objInstance.GetType().GetProperty(map.Key.ToString());
                    
                                if (propertyInfo != null)
                                {
                                    if (propertyInfo.PropertyType == typeof(string))
                                        propertyInfo.SetValue(objInstance, tableCell.ToString(), null);
                                    else if (propertyInfo.PropertyType == typeof(bool))
                                        propertyInfo.SetValue(objInstance, Convert.ToBoolean(tableCell), null);
                                    else if (propertyInfo.PropertyType == typeof(int))
                                        propertyInfo.SetValue(objInstance, Convert.ToInt32(tableCell), null);
                                    else if (propertyInfo.PropertyType == typeof(decimal))
                                        propertyInfo.SetValue(objInstance, Convert.ToDecimal(tableCell), null);
                                    else if (propertyInfo.PropertyType == typeof(double))
                                        propertyInfo.SetValue(objInstance, Convert.ToDouble(tableCell), null);
                                    else if (propertyInfo.PropertyType == typeof(DateTime))
                                        propertyInfo.SetValue(objInstance, Convert.ToDateTime(tableCell), null);
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            Helpers.LogException(ex);
                        }                       
                    }
                }
 
                col.Add(objInstance);
            }
 
            return col;
        }
    }

Open in new window

0
drichardsCommented:
I am not sure what your goal is, so I can't comment too much on the code.  I can say, however, that the reason your first code didn't work is that you did not ever retrieve the value of the Metadata property from the Product instance, so you can't set its value.  I've included a fix for that below.  Note that propertyParent is now the result of a GetValue call to retrieve the Metadata value from the Product instance.  I renamed the PropertyInfo to propertyParentInfo so it's clear what I did.

Additionally, you can't set the Keywords property of metadata to a double because it's a string property.  You could use the PropertyInfo.PropertyType value to figure out how to cast the value you want to set to the correct type.
            string propertyToAssign = "MetaData.Keywords";
            Product oProduct = new Product();
 
            PropertyInfo propertyParentInfo = oProduct.GetType().GetProperty(propertyToAssign.Split('.')[0]); //MetaData
            // Need to get the value of the Metadata property so we can set sub-parts
            object propertyParent = propertyParentInfo.GetValue(oProduct, null);
            PropertyInfo propertyInfo = propertyParentInfo.PropertyType.GetProperty(propertyToAssign.Split('.')[1]); //Keywords
 
            propertyInfo.SetValue(propertyParent, "Key1, Key2", null);

Open in new window

0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
.NET Programming

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.