NancyFx / Nancy

Lightweight, low-ceremony, framework for building HTTP based services on .Net and Mono
http://nancyfx.org
MIT License
7.15k stars 1.46k forks source link

IDynamicMetaObjectProvider not recognized by SuperSimpleViewEngine #2207

Open ralfw opened 8 years ago

ralfw commented 8 years ago

The SuperSimpleViewEngine is supposed to work with dynamic data implementing IDynamicMetaObjectProvider.

Here's how I create an object implementing this interface:

dynamic vm = Newtonsoft.Json.JsonConvert.DeserializeObject("{\"Name\":\"Mary\"}");
if ((vm is IDynamicMetaObjectProvider))
{
    Console.WriteLine ("is IDynamicMetaObjectProvider");
}

The message gets printed, the view model implements the interface.

However, when I pass it to a SuperSimpleViewEngine view (return View["myview.html", vm]) it does not get rendered.

First I thought, Json.NET does not implement any of the interfaces on its dynamic results. But as is shown above, it does.

Then I checked the source code of the SuperSimpleViewEngine and found this:

private static Tuple<bool, object> GetPropertyValue(object model, string propertyName)
{
    ...
    if (model is IDictionary<string, object>)
    {
         return DynamicDictionaryPropertyEvaluator(model, propertyName);
    }
    if (!(model is IDynamicMetaObjectProvider))
    {
        return StandardTypePropertyEvaluator(model, propertyName);
    }
    ...

This to me means: If a view model implements IDynamicMetaObjectProvider then it is not accepted by the view engine.

In fact this means, any other interface than IDictionary<string, object> is treated the same - except the IDynamicMetaObjectProvider interface. Especially DynamicDictionaryValue and DynamicObject - which get checked - later, will not be treated properly (unless the object also implements IDynamicMetaObjectProvider).

This looks strange if not wrong.

Could anyone please check?

aidapsibr commented 8 years ago

Just weighing in as a fellow user.

Looks to me like you are correct and this line should be IDynamicMetaObjectProvider instead of DynamicObject.

if (model is DynamicObject) //should be IDynamicMetaObjectProvider right?
{
    return GetDynamicMember(model, propertyName);
}
aidapsibr commented 8 years ago

Part of the problem may be from the fact that JObject inherits the interface IDynamicMetaObjectProvider two levels deep in JToken.

public abstract class JToken : IJEnumerable<JToken>, IEnumerable<JToken>,
    IEnumerable, IJsonLineInfo, ICloneable, IDynamicMetaObjectProvider

Implements IDynamicMetaObjectProvider

with this implementation:

protected virtual DynamicMetaObject GetMetaObject(Expression parameter)
{
    return new DynamicProxyMetaObject<JToken>(parameter, this, 
        new DynamicProxy<JToken>(), true);
}

and is inherited by

public abstract class JContainer : JToken, IList<JToken>, ICollection<JToken>, 
    IEnumerable<JToken>, IEnumerable, ITypedList, IBindingList, IList,
    ICollection, INotifyCollectionChanged

which is finally inherited by

public class JObject : JContainer, IDictionary<string, JToken>,
    ICollection<KeyValuePair<string, JToken>>, IEnumerable<KeyValuePair<string, JToken>>, 
    IEnumerable, INotifyPropertyChanged, ICustomTypeDescriptor, INotifyPropertyChanging

The documentation on types is abysmal, but the assembly doesn't lie.

However that does show that Nancy should at least attempt resolution (though my guess is that the implementation of JToken won't play nice).

JamesNK commented 8 years ago

The is operator doesn't care about inheritance. Json.NET implements IDynamicMetaObjectProvider correctly.

By the way, looking at the Nancy code I'll speak from my experience of combing reflection with dynamic: not caching the callsite in GetDynamicMember will give you garbage performance. You do not want to create it every time.

private static Tuple<bool, object> GetDynamicMember(object obj, string memberName)
{
    var binder = Microsoft.CSharp.RuntimeBinder.Binder.GetMember(CSharpBinderFlags.None, memberName, obj.GetType(),
        new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
    var callsite = CallSite<Func<CallSite, object, object>>.Create(binder);
    var result = callsite.Target(callsite, obj);
    return result == null ? new Tuple<bool, object>(false, null) : new Tuple<bool, object>(true, result);
}