Domanda

I have a Dictionary<string, object> which holds a property name as string and it's value as object. I also have a Bind method extension which, through reflection, sets that propery name with its corresponding value:

public static T Bind<T>(this T @this, 
                        Dictionary<string, object> newValues, 
                        params string[] exceptions) where T : class
{
    var sourceType = @this.GetType();
    foreach (var pair in newValues.Where(v => !exceptions.Contains(v.Key)))
    {
        var property = sourceType.GetProperty(pair.Key, 
                                              BindingFlags.SetProperty | 
                                              BindingFlags.Public      | 
                                              BindingFlags.Instance);
        var propType = Nullable.GetUnderlyingType(property.PropertyType) ?? 
                       property.PropertyType;
        property.SetValue(@this, (pair.Value == null) ? null : 
                                 Convert.ChangeType(pair.Value, propType), null);
    }
    return @this;
}

For instance, consider a class like this:

public class User
{
    public string   Name { get; set; }
    public DateTime Date { get; set; }
}

Everything runs fine, except when I got a class with a property name of another object, like this:

public class User
{
    public string   Name    { get; set; }
    public DateTime Date    { get; set; }
    public Address  Address { get; set; }
}

public class Address
{
    public string PostalCode { get; set; }
}

So, if I try to send a Name property name, ok, but I got problems with composite property names, like Address.PostalCode.

Can you advise a way to handle that situation?

EDIT #1:

To summarize the problem: calling sourceType.GetProperty("Name", ...) in the context of a User class instance correctly allows to set its value, but it doesn't work using a sourceType.GetProperty("Address.PostalCode", ...) in same instance.

EDIT #2: A more complete example should be:

var user   = new User{ Address = new Address() };
var values = new Dictionary<string, object>
{
    { "Name"              , "Sample"       },
    { "Date"              , DateTime.Today },
    { "Address.PostalCode", "12345"        } // Here lies the problem
}
user.Bind(values);
È stato utile?

Soluzione 3

I was able to solve it identifying if the property name have a period and recurring it:

public static T Bind<T>(this T @this,
                Dictionary<string, object> newValues,
                params string[] exceptions) where T : class
{
    var sourceType = @this.GetType();
    var binding = BindingFlags.Public | BindingFlags.Instance;
    foreach (var pair in newValues.Where(v => !exceptions.Contains(v.Key)))
    {
        if(pair.Key.Contains("."))
        {
            var property = sourceType.GetProperty(
                           pair.Key.Split('.').First(),
                           binding | BindingFlags.GetProperty);
            var value = property.GetValue(@this, null);
            value.Bind(new Dictionary<string, object>
            {
                { 
                    String.Join(".", pair.Key.Split('.').Skip(1).ToArray()),
                    pair.Value
                }
            });
        }
        else
        {
            var property = sourceType.GetProperty(pair.Key, 
                           binding | BindingFlags.SetProperty);
            var propType = Nullable.GetUnderlyingType(property.PropertyType) ??
                           property.PropertyType;
            property.SetValue(@this, (pair.Value == null) ? null :
                     Convert.ChangeType(pair.Value, propType), null);
        }
    }
    return @this;
}

Usage:

var user   = new User {Address = new Address{ User = new User() }};
var values = new Dictionary<string, object>()
{
    {"Name", "Sample"},
    {"Date", DateTime.Today},
    {"Address.PostalCode", "12345"},
    {"Address.User.Name", "Sub Sample"}
};

user.Bind(values);

public class User
{
    public string   Name     { get; set; }
    public DateTime Date     { get; set; }
    public Address  Address  { get; set; }
}

public class Address
{
    public string PostalCode { get; set; }
    public User   User       { get; set; }
}

Altri suggerimenti

My guess is that Convert.ChangeType only works for objects implementing IConvertible. Thus, I'd just add a check, and only use Convert.ChangeType if pair.Value has a type that implements IConvertible. Furthermore, afaik Convert does not use overloaded conversion operators, so you can save this check whenever pair.Value is not a struct, i.e.

object value;
if (pair.Value == null) {
    value = null;
} else {
    value = pair.Value.GetType().IsStruct ? Convert.ChangeType(pair.Value, propType) : pair.Value;
}
...

There are many binding engines out there, WPF, ASP.NET MVC, winforms in the core .NET and who knows how many others, you can check out all their source codes and documentation about their syntax.

Let's see the most simple case. Let's say that the variable X holds an object and you have the binding expression "A.B.C". Let's split up the binding path, the first part is "A". So you use reflection to get the property named "A" in X, and you put that other object into X. Now comes the second part, "B", so let's find a property named "B" in (the new) X. You find that, and put that into X. Now you get to the final part, "C", and now you can either read or write that property in X. The point is that you don't need recursion or anything, it's just a simple loop, you iterate over the parts of the binding expression, evaluate them, and you keep the current object in the same variable.

But the fact is that it can get much more complex than that. You could ask for array indexing, like "A.B[2].C". Or what if you have a path "A.B", and X.A is null, what do you do? Instantiate X.A, but what if it lacks a public parameterless constructor?

I want you to see that it can be a very complex problem. You have to specify a syntax and rules, and then implement that. You didn't specify in your question the exact syntax and rules you want to use. And if it happens to be more than the simple case I mentioned above, then the solution could be too lengthy.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top