dimanche 23 octobre 2016

How to set a nested property value based on a concatenated property string/path using reflection and recursion

I will try and explain this in order to provide context.

I have an xsd which I have converted into a class. The ultimate idea is to create an xml file based on this schema.

In order to create my 'object' to populate and write my xml file, I have opted to go the route of providing a table with property Key and Value which is completed on a UI.

In order to populate the table key column I have done the following:

public static void ReflectClass(
    object instance) 
{
    var baseType = instance.GetType();
    var props = ReflectClass2(baseType, baseType.Name);
    //Do whatever with props
}

private static IEnumerable<string> ReflectClass2(
    Type basetype,
    string baseName) 
    {

    var propsList = new List<string>();

    if (basetype.IsArray) 
    {
        basetype = basetype.GetElementType();
    }

    var instanceProps = basetype.GetProperties();

    foreach (var propertyInfo in instanceProps) 
    {
        var propInfo = propertyInfo;
        var name = propertyInfo.Name;
        var type = ListArgumentOrSelf(propertyInfo.PropertyType);

        if (!propertyInfo.PropertyType.IsValueType) 
        {
            foreach (var info in ReflectClass2(type, name)) 
            {
                propsList.Add(string.Format("{0}.{1}", baseName, info));
            }
        }
        else 
        {
            if (propertyInfo.CanWrite) //This is so that I don't get the char and length properties of string or int for example
            {
                propsList.Add(string.Format("{0}.{1}", baseName, propertyInfo.Name));
            }
            else 
            {
                if (!propsList.Exists(x => x == baseName)) 
                {
                    propsList.Add(string.Format("{0}", baseName));
                }
            }
        }
    }

    return propsList;
}

private static Type ListArgumentOrSelf(
    Type type) 
{
    if (!type.IsGenericType)
        return type;
    if (type.GetGenericTypeDefinition() != typeof(List<>))
        throw new Exception("Only List<T> are allowed");
    return type.GetGenericArguments()[0];
}

Let's say I have the following class structure:

class A 
{
    string Name {get; set;}
    B classB {get; set;}
}

class B
{
    int Age {get; set;}
    C[] classC {get; set;}
}

class C
{
    string Town {get; set;}
    string Suburb {get; set;}
    string Street {get; set;}
}

It should produce something like this:

A.Name
A.classB.Age
A.classB.classC.Town
A.classB.classC.Suburb
A.classB.classC.Street

I will then set the values of these properties as such:

A.Name | 'MyName'
A.classB.Age | '20'
A.classB.classC.Town[0] | 'MyTown'
A.classB.classC.Suburb[0] | 'MySuburb'
A.classB.classC.Street[0] | null

Now I want to create an object that will contain those set values so that I can serialize it to XML. So I need to get into my classes and find the properties represented by the strings and set those inside my object.

Not all properties would necessarily be set though as can be seen by the 'Street' property. So need to find the property that is referred to through the concatenated string and set it in the object.

I looked at this, but it retrieves the value, does not set it: Recursively find object property via reflection?

Also the object wont be completely instantiated, needs to do so as it traverses. Meaning that when it goes into classA and needs to go to classB, it needs to create a new instance of classB and set the property.

Take note that in classB there is an array of classC. So need to set the values within classC as many times as there is array/list items.

I hope that it makes sense. I would appreciate any help and even a better overall solution would be awesome.





Aucun commentaire:

Enregistrer un commentaire