Freitag, 2. Juli 2010

A slim reflection API to get / set property values

I am working with a datagrid that consumes dynmaic data. Due to the fact that Silverlight does not provide that functionallity I used the example of Vladimir Enchev from Telerik.

That leads to the fact that I dont know the type of the objects my grid is bound to at compile time, becuase the type is dynamically generated at runtime. I have the scenario that the user clicks to "Edit current entry", then a new dialog opens showing the details for the item selected within the grid. What I needed was a method that gives me the Value of the primary key field of this object.

To avoid writing reflection code every time, I intruduced two extender called GetPropertyValue and SetPropertyValue.

First of all I need to be able to get a PropertyInfo object from a given Property path. I wanted to support multi level pathes (such Person.Address.ZipCode). Indexed properties are not supported.

The code to retrieve the PropertyInfo is recursive. As long as the path contains a "." I get the value of the head property using GetPropertyValue extender and then get the next property for the rest paht using a recursive call to GetProperty:


public static PropertyInfo GetProperty(this object target, string path)
        {
            if ((target == null) || string.IsNullOrEmpty(path))
            {
                return null;
            }

            String CurrentPath = String.Empty;

            if (path.Contains('.'))
            {
                String[] pathParts = path.Split(new char[] { '.' });
                String restPath = path.StripLeft(pathParts[0].Length + 1);
                CurrentPath = pathParts[0];

                return target.GetPropertyValue<Object>(CurrentPath).GetProperty(restPath);
            }

            PropertyInfo property = target.GetType().GetProperty(path, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            return property;
        }

        public static T GetPropertyValue<T>(this object target, string path)
        {
            PropertyInfo property = target.GetProperty(path);
            if (property == null)
            {
                return default(T);
            }

            try
            {
                return property.GetValue(target, null).To<T>(default(T));
            }
            catch
            {
                return default(T);
            }
        }

Using if (path.Contains('.')) I check wether I need to make a recursive call. If so, the path is split up by the '.'. I then take the head of the path and the rest path. Then I do the recursive call:

return target.GetPropertyValue<Object>(CurrentPath).GetProperty(restPath);

If there is no dot in the path left I get the PropertyInfo using GetType().GetPropert(path). It was important to me to get the Property using a case invariant path, so 'Name' and 'NAME' should both retrieve the 'Name' property, so I had to provide the correct binding flags.

The GetPropertyValue extender then just delivers the value of the property via the normal reflection api. To get the correct type I use the To() extender:

        public static T To<T>(this object Value, T DefaultValue)
        {
            try
            {
                // Check if target type is a nullable
                Type TypeOfT = typeof(T);
                if (TypeOfT.IsGenericType && TypeOfT.GetGenericTypeDefinition() == typeof(Nullable<>))
                { 
                    // Check if value is not a nullable type
                    Type TypeOfValue = Value.GetType();
                    if (!TypeOfValue.IsGenericType || !(TypeOfValue.GetGenericTypeDefinition() == typeof(Nullable<>)))
                    { 
                        // Target type is a nullable, but value type is not => cast the value to the nullables target type first
                        Type TypeOfNullable = TypeOfT.GetGenericArguments()[0];
                        Object ValueInTargetType = Convert.ChangeType(Value, TypeOfNullable, LanguageContext.DefaultInstance.Culture);

                        // Return converted value as Nullable<TypeOfValue>
                        return (T)ValueInTargetType;
                    }
                }

                // Either both types are nullable or neither types are nullable, so just convert them
                return (T)Convert.ChangeType(Value, typeof(T), LanguageContext.DefaultInstance.Culture);
            }
            catch (Exception ex)
            {
                return DefaultValue;
            }
        }

This code can handle the conversion of nullable types to its underlying type and vice versa as well.

Having this in place, SetPropertyValue is easy:

public static void SetPropertyValue<T>(this object target, string path, T value)
        {
            PropertyInfo property = target.GetProperty(path);
            if (property == null)
            {
                return;
            }

            try
            {
                property.SetValue(target, value, null);
            }
            catch
            {
                return;
            }
        }

Actually I am thinking about a small fluent reflection API that hides you from all the GetType() kind of stuff. I will blog about this another time.

Have fun!.

1 Kommentar: