Payloads as dynamic Objects in ASP.NET MVC

Even though the dynamic type seems to be everywhere these days, ASP.NET MVC still doesn’t support having dynamic parameters in controller action methods out of the box; which is to say, this doesn’t work as expected:

[HttpPost]

public ActionResult Post(dynamic payload)

{

    //payload will be an object without any properties

 

    return this.View();

}

However, because MVC is so extensible, it is very easy to achieve it. For that, we need to build a custom model binder and apply it to our action method parameter. We’ll assume that the content will come as JSON from the HTTP POST payload. Note that this does not happen with Web API, but still happens with MVC Core!

There are a couple of ways by which we can bind a model binder to a parameter:

First, let’s focus on the actual model binder, the core for any of the above solutions; we need to implement the IModelBinder interface, which isn’t really that hard to do:

public sealed class DynamicModelBinder : IModelBinder

{

    private const string ContentType = "application/json";

 

    public DynamicModelBinder(bool useModelName = false)

    {

        this.UseModelName = useModelName;

    }

 

    public bool UseModelName { get; private set; }

 

    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)

    {

        dynamic data = null;

 

        if ((controllerContext.HttpContext.Request.AcceptTypes.Any(x => x.StartsWith(ContentType, StringComparison.OrdinalIgnoreCase) == true) &&

            (controllerContext.HttpContext.Request.ContentType.StartsWith(ContentType, StringComparison.OrdinalIgnoreCase) == true)))

        {

            controllerContext.HttpContext.Request.InputStream.Position = 0;

 

            using (var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream))

            {

                var payload = reader.ReadToEnd();

 

                if (string.IsNullOrWhiteSpace(payload) == false)

                {

                    data = JsonConvert.DeserializeObject(payload);

 

                    if (this.UseModelName == true)

                    {

                        data = data[bindingContext.ModelName];

                    }

                }

            }

        }

 

        return data;

    }

}

Nothing fancy here; it will check to see if both the Accept and the Content-Type HTTP headers are present and set to application/json, the official MIME type for JSON, before parsing the posted content. If any content is present, JSON.NET will parse it into it’s own object. The UseModelName property is used to bind to a specific property of the payload, for example, say you are binding to a parameter called firstName, and you want it populated with the contents of the firstName field in the payload. In our case, we don’t need it, we want the whole thing, so it is set to false.

Now, the way I recommend for applying this model binder is through a custom attribute:

[Serializable]

[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]

public sealed class DynamicModelBinderAttribute : CustomModelBinderAttribute

{

    public DynamicModelBinderAttribute(bool useModelName = false)

    {

        this.UseModelName = useModelName;

    }

 

    public bool UseModelName { get; private set; }

 

    public override IModelBinder GetBinder()

    {

        return new DynamicModelBinder(this.UseModelName);

    }

}

It goes like this:

[HttpPost]

public ActionResult Post([DynamicModelBinder] dynamic payload)

{

    //payload will be populated with the contents of the HTTP POST payload

 

    return this.View();

}

Or, if you want to do it for several dynamic parameters, just set a global model binder provider for type Object; this is safe, because we would never have a parameter of type Object:

public sealed class DynamicModelBinderProvider : IModelBinderProvider

{

    public static readonly IModelBinderProvider Instance = new DynamicModelBinderProvider();

 

    public IModelBinder GetBinder(Type modelType)

    {

        if (modelType == typeof (object))

        {

            return new DynamicModelBinder();

        }

 

        return null;

    }

}

And we register is as this:

ModelBinderProviders.BinderProviders.Insert(0, DynamicModelBinderProvider.Instance);

And that’s it. This is one of what I consider to be ASP.NET MVC flaws, and they will deserve another post, soon. Web API already solves this problem, but it is still there in the future version of MVC Core, and can be solved in the same way.

Detecting Default Values of Value Types

Func<object, bool> func = (obj) => obj.Equals(default(Int32));

Func<object, bool> func = (obj) => obj.Equals(default(Int32));

Introduction

I don’t know if this happened to you: the need to find out if some instance of a class is the class’ default value. For reference types – which include nullables -, it is always null, and for value types, it is the value that you get if you do not initialize a field of that type or if you call its default parameterless constructor – false for Boolean, 0 for numbers, the 0 member of an enumeration, etc. So, the problem is, how can we tell if some instance represents this default value, dynamically, that is, for any value type, not just a specific one.

Again, nothing special, just the result of a lazy Saturday afternoon, still, hope you see some value in it! Winking smile

Take 0: Direct Comparison

How would we compare a built-in type instance, like int, with its default value? Just compare it with 0. This is useful to see how each of the other techniques compare to it.

static bool IsDefaultDirect(int obj)

{

    return obj.Equals(0);

}

Take 1: Comparison With a New Instance

An easy way is to compare the value we have with a new instance of the same type, like this:

static bool IsDefaultUsingConstruction(ValueType obj)

{

    return obj.Equals(Activator.CreateInstance(obj.GetType()));

}

Activator.CreateInstance knows how to create a default instance of a value type, so we’re good.

Take 2: Using Generics Directly

Another option is to use the ability to compare a generic variable with the default value for the generic type. This cannot be used in exactly the same way as #1, because here we need to explicitly call the comparison method with a generic parameter:

static bool IsDefaultUsingGeneric<T>(T obj) where T : struct

{

    return obj.Equals(default(T));

}

Notice the struct constraint, it is exactly the same as declaring the parameter as ValueType, because it is the base class for all value types.

Take 3: Using Generics Dynamically

You can make the previous example dynamic, with the cost of an additional method invocation, like this:

static bool IsDefaultUsingReflection(ValueType obj)

{

    //cache this, please

    var isDefaultUsingGenericMethod = typeof(Program).GetMethod("IsDefaultUsingGeneric", BindingFlags.Static | BindingFlags.NonPublic);

    var method = isDefaultUsingGenericMethod.MakeGenericMethod(obj.GetType());

    return (bool) method.Invoke(null, new object[] { obj });

}

Take 4: Using a LINQ Expression Bound to a Specific Type

Another option is to dynamically compile a LINQ expression that performs the comparison, something like this:

Func<T, bool> func = (obj) => obj.Equals(default(T));

We can create this expression dynamically, and bind it to the desired value type:

static bool IsDefaultUsingLinq(ValueType obj)

{

    var argType = obj.GetType();

    var arguments = new Expression[] { Expression.Default(argType) };

    var paramExpression = Expression.Parameter(argType, "x");

 

    var equalsMethod = argType.GetMethod("Equals", new Type[] { argType });

    var call = Expression.Call(paramExpression, equalsMethod, arguments);

 

    var lambdaArgType = typeof(Func<,>).MakeGenericType(argType, typeof(bool));

    var lambdaMethod = LambdaMethod.MakeGenericMethod(lambdaArgType);

 

    var expression = lambdaMethod.Invoke(null, new object[] { call, new ParameterExpression[] { paramExpression } }) as LambdaExpression;

 

    //cache this, please

    Delegate func = expression.Compile();

 

    return (bool)func.DynamicInvoke(obj);

}

Take 5: Using a LINQ Expression Bound to Object

A very similar option to #4 is to use Object.Equals instead of the value type’s specific Equals method, like this:

Func<object, bool> func = (obj) => obj.Equals(default(int));

Of course, the int parameter depends on the actual type of the value type parameter being passed:

static readonly MethodInfo LambdaMethod = typeof(Expression).GetMethods(BindingFlags.Static | BindingFlags.Public).First(x => x.Name == "Lambda" && x.GetParameters()[1].ParameterType == typeof(ParameterExpression[]));

static readonly MethodInfo EqualsMethod = typeof (object).GetMethod("Equals", BindingFlags.Instance | BindingFlags.Public);

 

static bool IsDefaultUsingLinqObject(ValueType obj)

{

    var argType = typeof(object);

    var arguments = new Expression[] { Expression.Convert(Expression.Default(obj.GetType()), argType) };

    var equalsMethod = EqualsMethod;

    var paramExpression = Expression.Parameter(argType, "x");

    var call = Expression.Call(paramExpression, equalsMethod, arguments);

    var lambdaArgType = typeof(Func<object, bool>);

    var lambdaMethod = LambdaMethod.MakeGenericMethod(lambdaArgType);

    var expression = lambdaMethod.Invoke(null, new object[] { call, new ParameterExpression[] { paramExpression } }) as Expression<Func<object, bool>>;

 

    //cache this, please

    Func<object, bool> func = expression.Compile();

 

    return func(obj);

}

Because the comparison expression, of type Func<object, bool>, is strongly typed, we avoid the need to call Delegate.DynamicInvoke, the performance increases substantially.

Take 6: Using Formatter Services

A long, long time ago, in a distance galaxy, I already mentioned, en passant, the usage of FormatterServices.GetUninitializedObject to create instances of a type. Picking up example #1, let’s replace Activator.CreateInstance by FormatterServices.GetUninitializedObject and see the gains:

static bool IsDefaultUsingFormatterServices(ValueType obj)

{

    return obj.Equals(FormatterServices.GetUninitializedObject(obj.GetType()));

}

Take 7: Using a LINQ Expression Bound to a Specific Type and Using Invocation Through Dynamics

What a long name… Smile Well, this one is identical to #4, but without Delegate.DynamicInvoke. Instead, I make use of the dynamic type’s late binding to invoke the delegate, which results in even better performance:

static readonly MethodInfo LambdaMethod = typeof(Expression).GetMethods(BindingFlags.Static | BindingFlags.Public).First(x => x.Name == "Lambda" && x.GetParameters()[1].ParameterType == typeof(ParameterExpression[]));

 

static bool IsDefaultUsingLinqAndDynamic(ValueType obj)

{

    var argType = obj.GetType();

    var arguments = new Expression[] { Expression.Default(argType) };

    var paramExpression = Expression.Parameter(argType, "x");

    var equalsMethod = argType.GetMethod("Equals", new Type[] { argType });

    var call = Expression.Call(paramExpression, equalsMethod, arguments);

    var lambdaArgType = typeof(Func<,>).MakeGenericType(argType, typeof(bool));

    var lambdaMethod = LambdaMethod.MakeGenericMethod(lambdaArgType);

    var expression = lambdaMethod.Invoke(null, new object[] { call, new ParameterExpression[] { paramExpression } }) as LambdaExpression;

 

    //cache this, please

    Delegate func = expression.Compile();

 

    dynamic arg = obj;

    dynamic del = func;

 

    return del(arg);

}

Measuring

I put in two methods for measuring calls and doing averages:

static long MeasureTicks(Action action)

{

    var watch = Stopwatch.StartNew();

 

    action();

 

    return watch.ElapsedTicks;

}

 

static float Measure(int times, Action action)

{

    var avg = 0L;

 

    for (var i = 0; i < times; ++i)

    {

        avg += MeasureTicks(action);

    }

 

    return (float)avg / times;

}

I used a Stopwatch to obtain the ElapsedTicks of the method to be exercised. I changed the methods I presented, namely, #4, #5 and #7, so as to cache the types and delegates created dynamically, this is crucial, and I leave that as an exercise to you – just remember that each method can potencially be called with different values, of different types. Then I added a warm-up step, which exercises the code using an integer parameter:

static void Warmup(int value)

{

    var times = 1;

    Measure(times, () => IsDefaultDirect(value));

    Measure(times, () => IsDefaultUsingConstruction(value));

    Measure(times, () => IsDefaultUsingGeneric(value));

    Measure(times, () => IsDefaultUsingReflection(value));

    Measure(times, () => IsDefaultUsingLinq(value));

    Measure(times, () => IsDefaultUsingLinqObject(value));

    Measure(times, () => IsDefaultUsingFormatterServices(value));

    Measure(times, () => IsDefaultUsingLinqAndDynamic(value));

}

In the past, I learned that a warm-up method – or lack of it – makes a huge difference.

I executed each option 100 times and got its results:

static void Measure()

{

    var times = 100;

    var value = 100;

 

    Warmup(value);

 

    var m0 = Measure(times, () => IsDefaultDirect(value));

    var m1 = Measure(times, () => IsDefaultUsingConstruction(value));

    var m2 = Measure(times, () => IsDefaultUsingGeneric(value));

    var m3 = Measure(times, () => IsDefaultUsingReflection(value));

    var m4 = Measure(times, () => IsDefaultUsingLinq(value));

    var m5 = Measure(times, () => IsDefaultUsingLinqObject(value));

    var m6 = Measure(times, () => IsDefaultUsingFormatterServices(value));

    var m7 = Measure(times, () => IsDefaultUsingLinqAndDynamic(value));

}

The results I got were:


Method Ticks Difference
#0: Direct Comparison 1.82 131.88%
#1: Comparison With a New Instance 1.92 139.13%
#2: Using Generics Directly 1.46 105.80%
#3: Using Generics Dynamically 6.9 500%
#4: Using a LINQ Expression Bound to a Specific Type 3.05 221.01%
#5: Using a LINQ Expression Bound to Object 1.61 116.67%
#6: Using Formatter Services 1.53
#7: Using a LINQ Expression Bound to a Specific Type and Using Invocation Through Dynamics 1.38 100%

Conclusion

I was really surprised that the direct comparison is actually – at least for integers – not the best way to see if a value is the default for its type! There’s a big range in results, and I can say that I was expecting that for #3. I knew that FormatterServices.GetUninitializedObject would give better results than Activator.CreateInstance, but I imagine this cannot be used with all types, because it doesn’t run the type’s constructor, possibly skipping some default initializations. I also knew that the performance of Delegate.DynamicInvoke is less than ideal, but it was interesting to see that dynamics can improve it.

As always, I’d love to see what you have to say! Do you see flaws in my approach, or do you know of any better solutions? Fire away! Winking smile

Elastic Object Implementation in .NET

I think it was Anoop Madhusudanan (aka, amazedsaint) who first coined the term “Elastic Object”. He even built an implementation, which you can find on GitHub and NuGet, which is very nice. Basically, it’s a class that allows adding members to its instances dynamically; this is a widely known pattern in scripting languages such as JavaScript, but not so easy to implement in .NET.

Now, I wanted to have something like this but also add some custom features that were missing, so I started something from scratch – yes, for the good or for the bad, my code is totally different from amazedsaint’s). Of course, both leverage the dynamic type, that’s where the whole idea came from.

Here are some examples of what we can do:

dynamic obj = new ElasticObject();

obj.A = 1;    //obj["A"] = 1;

obj.A.B = "1";

var props = TypeDescriptor.GetProperties(obj);    //"A"

 

dynamic obj = new ElasticObject(new { A = 1 });

int i = obj.A;

int ni = ~obj.A;

int ii = --obj.A;

bool b = obj.A;

 

dynmic obj = new ElasticObject(1);

var clone = obj.Clone();

(obj as INotifyPropertyChanged).PropertyChanged += (s, e) => { };

 

dynamic obj = new ElasticObject();

obj.A = 1;    //obj["A"] = 1;

obj.A.B = "1";

var path = obj.A.B["$path"];      //"A.B"

var parent = obj.A.B["$parent"];  //obj.A

var value = obj.A.B["$value"];    //"1"

var type = obj.A.B["$type"];      //string

Basically, I Inherit from DynamicObject and I use an internal dictionary for storing all dynamically assigned values, which will, in turn, be also ElasticObjects. I also allow for a “self value”, which will be the ElasticObject’s main value, in case no additional properties are supplied. I also provide implementations for some typical .NET interfaces, like INotifyPropertyChanged and ICloneable, and supply my own TypeDescriptionProvider which takes into account the dynamic properties and also a custom TypeConverter.

There are some provided properties that grant us access to the ElasticObject’s internals:

  • $Root: the root ElasticObject instance;
  • $Parent: the parent ElasticObject instance for the current one;
  • $Path: the full property path from the current object until the root;
  • $Value: the self value;
  • $Type: the type of the self value.

Without further discussion, here is the code for my ElasticObject class:

[Serializable]

[TypeConverter(typeof(ElasticObjectTypeConverter))]

[TypeDescriptionProvider(typeof(ElasticObjectTypeDescriptionProvider))]

public sealed class ElasticObject : DynamicObject, IDictionary<String, Object>, ICloneable, INotifyPropertyChanged

{

    private static readonly String [] SpecialKeys = new String[] { "$Path", "$Parent", "$Root", "$Value", "$Type" };

    private readonly IDictionary<String, Object> values = new Dictionary<String, Object>();

    private Object value;

    private ElasticObject parent;

 

    public ElasticObject() : this(null, null)

    {

    }

 

    internal ElasticObject(ElasticObject parent, Object value)

    {

        this.parent = parent;

        this.value = (value is ElasticObject) ? ((ElasticObject)value).value : value;

    }

 

    public ElasticObject(Object value) : this(null, value)

    {

    }

 

    public override String ToString()

    {

        if (this.value != null)

        {

            return (this.value.ToString());

        }

        else

        {

            var dict = this as IDictionary<String, Object>;

            return (String.Format("{{{0}}}", String.Join(", ", dict.Keys.Zip(dict.Values, (k, v) => String.Format("{0}={1}", k, v)))));

        }

    }

 

    public override Int32 GetHashCode()

    {

        if (this.value != null)

        {

            return (this.value.GetHashCode());

        }

        else

        {

            return (base.GetHashCode());

        }

    }

 

    public override Boolean Equals(Object obj)

    {

        if (Object.ReferenceEquals(this, obj) == true)

        {

            return (true);

        }

 

        var other = obj as ElasticObject;

 

        if (other == null)

        {

            return (false);

        }

 

        if (Object.Equals(other.value, this.value) == false)

        {

            return (false);

        }

 

        return (this.values.SequenceEqual(other.values));

    }

 

    public override IEnumerable<String> GetDynamicMemberNames()

    {

        return (this.values.Keys.Concat((this.value != null) ? TypeDescriptor.GetProperties(this.value).OfType<PropertyDescriptor>().Select(x => x.Name) : Enumerable.Empty<String>()));

    }

 

    public override Boolean TryBinaryOperation(BinaryOperationBinder binder, Object arg, out Object result)

    {

        if (binder.Operation == ExpressionType.Equal)

        {

            result = Object.Equals(this.value, arg);

            return (true);

        }

        else if (binder.Operation == ExpressionType.NotEqual)

        {

            result = !Object.Equals(this.value, arg);

            return (true);

        }

 

        return (base.TryBinaryOperation(binder, arg, out result));

    }

 

    public override Boolean TryUnaryOperation(UnaryOperationBinder binder, out Object result)

    {

        if (binder.Operation == ExpressionType.Increment)

        {

            if (this.value is Int16)

            {

                result = (Int16)value + 1;

                return (true);

            }

            else if (this.value is Int32)

            {

                result = (Int32)value + 1;

                return (true);

            }

            else if (this.value is Int64)

            {

                result = (Int64)value + 1;

                return (true);

            }

            else if (this.value is UInt16)

            {

                result = (UInt16)value + 1;

                return (true);

            }

            else if (this.value is UInt32)

            {

                result = (UInt32)value + 1;

                return (true);

            }

            else if (this.value is UInt64)

            {

                result = (UInt64)value + 1;

                return (true);

            }

            else if (this.value is Decimal)

            {

                result = (Decimal)value + 1;

                return (true);

            }

            else if (this.value is Single)

            {

                result = (Single)value + 1;

                return (true);

            }

            else if (this.value is Double)

            {

                result = (Double)value + 1;

                return (true);

            }

        }

        else if (binder.Operation == ExpressionType.Decrement)

        {

            if (this.value is Int16)

            {

                result = (Int16)value - 1;

                return (true);

            }

            else if (this.value is Int32)

            {

                result = (Int32)value - 1;

                return (true);

            }

            else if (this.value is Int64)

            {

                result = (Int64)value - 1;

                return (true);

            }

            else if (this.value is UInt16)

            {

                result = (UInt16)value - 1;

                return (true);

            }

            else if (this.value is UInt32)

            {

                result = (UInt32)value - 1;

                return (true);

            }

            else if (this.value is UInt64)

            {

                result = (UInt64)value - 1;

                return (true);

            }

            else if (this.value is Decimal)

            {

                result = (Decimal)value - 1;

                return (true);

            }

            else if (this.value is Single)

            {

                result = (Single)value - 1;

                return (true);

            }

            else if (this.value is Double)

            {

                result = (Double)value - 1;

                return (true);

            }

        }

        else if (binder.Operation == ExpressionType.Not)

        {

            if (this.value is Boolean)

            {

                result = !(Boolean)value;

                return (true);

            }

        }

        else if (binder.Operation == ExpressionType.OnesComplement)

        {

            if (this.value is Int16)

            {

                result = ~(Int16)value;

                return (true);

            }

            else if (this.value is Int32)

            {

                result = ~(Int32)value;

                return (true);

            }

            else if (this.value is Int64)

            {

                result = ~(Int64)value;

                return (true);

            }

            else if (this.value is UInt16)

            {

                result = ~(UInt16)value;

                return (true);

            }

            else if (this.value is UInt32)

            {

                result = ~(UInt32)value;

                return (true);

            }

            else if (this.value is UInt64)

            {

                result = ~(UInt64)value;

                return (true);

            }

        }

 

        return base.TryUnaryOperation(binder, out result);

    }

 

    public override Boolean TryInvokeMember(InvokeMemberBinder binder, Object[] args, out Object result)

    {

        var method = this.GetType().GetMethod(binder.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

 

        if (method == null)

        {

            foreach (var type in this.GetType().GetInterfaces())

            {

                method = type.GetMethod(binder.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

 

                if (method != null)

                {

                    break;

                }

            }

        }

 

        if (method != null)

        {

            result = method.Invoke(this, args);

 

            return (true);

        }

        else

        {

            return (base.TryInvokeMember(binder, args, out result));

        }

    }

 

    public override Boolean TryConvert(ConvertBinder binder, out Object result)

    {

        if (this.value != null)

        {

            if (binder.Type.IsInstanceOfType(this.value) == true)

            {

                result = this.value;

                return (true);

            }

            else if (binder.Type.IsEnum == true)

            {

                result = Enum.Parse(binder.Type, this.value.ToString());

                return (true);

            }

            else if ((typeof(IConvertible).IsAssignableFrom(binder.Type) == true) && (typeof(IConvertible).IsAssignableFrom(this.value.GetType()) == true))

            {

                result = Convert.ChangeType(this.value, binder.Type);

                return (true);

            }

            else if (binder.Type == typeof(String))

            {

                result = this.value.ToString();

                return (true);

            }

            else

            {

                var converter = TypeDescriptor.GetConverter(binder.Type);

 

                if (converter.CanConvertFrom(this.value.GetType()) == true)

                {

                    result = converter.ConvertFrom(this.value);

                    return (true);

                }

            }

        }

        else if (binder.Type.IsClass == true)

        {

            result = null;

            return (true);

        }

 

        result = null;

        return (false);

    }

 

    public override Boolean TrySetMember(SetMemberBinder binder, Object value)

    {

        (this as IDictionary<String, Object>)[binder.Name] = value;

 

        return (true);

    }

 

    public override Boolean TryGetMember(GetMemberBinder binder, out Object result)

    {

        if (this.value != null)

        {

            var prop = TypeDescriptor.GetProperties(this.value)[binder.Name];

 

            if (prop != null)

            {

                result = prop.GetValue(this.value);

                return (true);

            }

        }

 

        return (this.values.TryGetValue(binder.Name, out result));

    }

 

    public override Boolean TrySetIndex(SetIndexBinder binder, Object[] indexes, Object value)

    {

        if ((indexes.Count() != 1) || (indexes.First() == null))

        {

            return (false);

        }

 

        var key = indexes.First() as String;

 

        if (indexes.First() is Int32)

        {

            var index = (Int32)indexes.First();

 

            if (this.values.Count < index)

            {

                key = this.values.ElementAt(index).Key;

            }

        }

        else if (key == null)

        {

            return (false);

        }

 

        (this as IDictionary<String, Object>)[key] = value;

 

        return (true);

    }

 

    public override Boolean TryGetIndex(GetIndexBinder binder, Object[] indexes, out Object result)

    {

        if ((indexes.Count() != 1) || (indexes.First() == null))

        {

            result = null;

            return (false);

        }

 

        var key = indexes.First() as String;

 

        if (key != null)

        {

            if (this.value != null)

            {

                var prop = TypeDescriptor.GetProperties(this.value)[key];

 

                if (prop != null)

                {

                    result = prop.GetValue(this.value);

                    return (true);

                }

            }

 

            if (String.Equals("$parent", key, StringComparison.InvariantCultureIgnoreCase) == true)

            {

                result = this.parent;

                return (true);

            }

            else if (String.Equals("$value", key, StringComparison.InvariantCultureIgnoreCase) == true)

            {

                result = this.value;

                return (true);

            }

            else if (String.Equals("$type", key, StringComparison.InvariantCultureIgnoreCase) == true)

            {

                result = ((this.value != null) ? this.value.GetType() : null);

                return (true);

            }

            else if (String.Equals("$root", key, StringComparison.InvariantCultureIgnoreCase) == true)

            {

                var root = this;

 

                while (root != null)

                {

                    if (root.parent == null)

                    {

                        break;

                    }

 

                    root = root.parent;

                }

 

                result = root;

                return (true);

            }

            else if (String.Equals("$path", key, StringComparison.InvariantCultureIgnoreCase) == true)

            {

                var list = new LinkedList<string>();

 

                var p = this.parent;

                var previous = (Object)this;

 

                while (p != null)

                {

                    var kv = p.values.SingleOrDefault(x => (Object)x.Value == (Object)previous);

 

                    list.AddFirst(kv.Key);

 

                    previous = ((ElasticObject)kv.Value).parent;

                    p = p.parent;

                }

 

                result = String.Join(".", list);

                return (true);

            }

            else

            {

                return (this.values.TryGetValue(key, out result));

            }

        }

        else if (indexes.First() is Int32)

        {

            var index = (Int32)indexes.First();

 

            if (this.values.Count < index)

            {

                result = this.values.ElementAt(index).Value;

                return (true);

            }

        }

 

        result = null;

        return (false);

    }

 

    void IDictionary<String,Object>.Add(String key, Object value)

    {

        (this as IDictionary<String,Object>)[key] = value;

    }

 

    Boolean IDictionary<String,Object>.ContainsKey(String key)

    {

        return (this.GetDynamicMemberNames().Contains(key));

    }

 

    ICollection<String> IDictionary<String,Object>.Keys

    {

        get

        {

            return (this.GetDynamicMemberNames().ToList());

        }

    }

 

    Boolean IDictionary<String,Object>.Remove(String key)

    {

        return (this.values.Remove(key));

    }

 

    Boolean IDictionary<String,Object>.TryGetValue(String key, out Object value)

    {

        if (this.value != null)

        {

            var prop = TypeDescriptor.GetProperties(this.value)[key];

 

            if (prop != null)

            {

                value = prop.GetValue(this.value);

                return (true);

            }

        }

 

        return (this.values.TryGetValue(key, out value));

    }

 

    ICollection<Object> IDictionary<String,Object>.Values

    {

        get

        {

            return (this.values.Values.Concat((this.value != null) ? TypeDescriptor.GetProperties(this.value).OfType<PropertyDescriptor>().Select(x => x.GetValue(this.value)) : Enumerable.Empty<Object>()).ToList());

        }

    }

 

    Object IDictionary<String,Object>.this[String key]

    {

        get

        {

            if (this.value != null)

            {

                var prop = TypeDescriptor.GetProperties(this.value)[key];

 

                if (prop != null)

                {

                    return (prop.GetValue(this.value));

                }

            }

 

            return (this.values[key]);

        }

        set

        {

            if (value is ElasticObject)

            {

                this.values[key] = value;

                ((ElasticObject) value).parent = this;

            }

            else if (value == null)

            {

                this.values[key] = null;

            }

            else

            {

                this.values[key] = new ElasticObject(this, value);

            }

 

            this.OnPropertyChanged(new PropertyChangedEventArgs(key));

        }

    }

 

    private void OnPropertyChanged(PropertyChangedEventArgs e)

    {

        var handler = this.PropertyChanged;

 

        if (handler != null)

        {

            handler(this, e);

        }

    }

 

    void ICollection<KeyValuePair<String, Object>>.Add(KeyValuePair<String, Object> item)

    {

        (this as IDictionary<String, Object>)[item.Key] = item.Value;

    }

 

    void ICollection<KeyValuePair<String, Object>>.Clear()

    {

        this.values.Clear();

    }

 

    Boolean ICollection<KeyValuePair<String, Object>>.Contains(KeyValuePair<String, Object> item)

    {

        return (this.values.Contains(item));

    }

 

    void ICollection<KeyValuePair<String, Object>>.CopyTo(KeyValuePair<String, Object>[] array, Int32 arrayIndex)

    {

        this.values.CopyTo(array, arrayIndex);

    }

 

    Int32 ICollection<KeyValuePair<String, Object>>.Count

    {

        get

        {

            return (this.values.Count);

        }

    }

 

    Boolean ICollection<KeyValuePair<String,Object>>.IsReadOnly

    {

        get

        {

            return (this.values.IsReadOnly);

        }

    }

 

    Boolean ICollection<KeyValuePair<String,Object>>.Remove(KeyValuePair<String, Object> item)

    {

        return (this.values.Remove(item));

    }

 

    IEnumerator<KeyValuePair<String, Object>> IEnumerable<KeyValuePair<String,Object>>.GetEnumerator()

    {

        return (this.values.GetEnumerator());

    }

 

    IEnumerator IEnumerable.GetEnumerator()

    {

        return ((this as IDictionary<String, Object>).GetEnumerator());

    }

 

    public event PropertyChangedEventHandler PropertyChanged;

 

    Object ICloneable.Clone()

    {

        var clone = new ElasticObject(null, this.value) as IDictionary<String, Object>;

 

        foreach (var key in this.values.Keys)

        {

            clone[key] = (this.values[key] is ICloneable) ? (this.values[key] as ICloneable).Clone() : this.values[key];

        }

 

        return (clone);

    }

}

Granted, there may be some inefficiencies, I leave it to you, dear reader, the task to spot them and letting me know!

Now, the  TypeDescriptionProvider classes:

[Serializable]

public sealed class ElasticObjectTypeDescriptionProvider : TypeDescriptionProvider

{

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, Object instance)

    {

        return (new ElasticObjectTypeDescriptor(instance));

    }

}

 

[Serializable]

public sealed class ElasticObjectTypeDescriptor : CustomTypeDescriptor

{

    private readonly ElasticObject instance;

 

    public ElasticObjectTypeDescriptor(Object instance)

    {

        this.instance = instance as ElasticObject;

    }

 

    public override PropertyDescriptorCollection GetProperties()

    {

        if (this.instance != null)

        {

            return new PropertyDescriptorCollection((this.instance as IDictionary<String, Object>).Keys.Select(x => new ElasticObjectPropertyDescriptor(x)).ToArray());

        }

        else

        {

            return (base.GetProperties());

        }

    }

 

    public override TypeConverter GetConverter()

    {

        return (new ElasticObjectTypeConverter());

    }

 

    public override AttributeCollection GetAttributes()

    {

        return (new AttributeCollection(new SerializableAttribute()));

    }

}

And a custom converter:

[Serializable]

public sealed class ElasticObjectTypeConverter : TypeConverter

{

    public override Boolean CanConvertFrom(ITypeDescriptorContext context, Type sourceType)

    {

        return (true);

    }

 

    public override Boolean CanConvertTo(ITypeDescriptorContext context, Type destinationType)

    {

        if (destinationType == typeof(ElasticObject))

        {

            return (true);

        }

        else

        {

            return (base.CanConvertTo(context, destinationType));

        }

    }

 

    public override Object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)

    {

        if (value is ElasticObject)

        {

            return (value);

        }

        else

        {

            return (new ElasticObject(null, value));

        }

    }

 

    public override Object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, Object value, Type destinationType)

    {

        if (destinationType == typeof(ElasticObject))

        {

            return (this.ConvertFrom(context, culture, value));

        }

        else

        {

            return base.ConvertTo(context, culture, value, destinationType);

        }

    }

 

    public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, Object value, Attribute[] attributes)

    {

        var provider = TypeDescriptor.GetProvider(value) as TypeDescriptionProvider;

        var descriptor = provider.GetTypeDescriptor(value);

        return (descriptor.GetProperties(attributes));

    }

 

    public override Boolean GetPropertiesSupported(ITypeDescriptorContext context)

    {

        return (true);

    }

 

    public override Object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)

    {

        dynamic obj = new ElasticObject();

 

        foreach (var key in propertyValues.Keys)

        {

            obj[key.ToString()] = propertyValues[key];

        }

 

        return (obj);

    }

 

    public override Boolean GetCreateInstanceSupported(ITypeDescriptorContext context)

    {

        return (true);

    }

 

    public override Boolean GetStandardValuesSupported(ITypeDescriptorContext context)

    {

        return (false);

    }

}

That’s about it. Have fun and make sure you send me your thoughts!