jdiamond/Nustache — pull request #46 by mrydengren (2013-02-26 20:28:56+00:00)
Title: Provided a hook for adding additional value getters.

I've added a hook for adding additional `ValueGetter`:s. I've extracted the static factory method for each `ValueGetter` to its own class which derives from `ValueGetterFactory` and added a global `ValueGetterFactories.Factories` class and property to which it is possible to add additional `ValueGetter`:s.

This is the least intrusive approach I could think of and it mimics the approach used for `ValueProviderFactories` in ASP.NET MVC.

Originally I wanted to add support for dynamic objects, but since Nustache is compiled using the .NET 2.0 compiler and I didn't want to go down the route of adding support for multiple framework versions this was the compromise I decided on.

Using this approach I can add support for dynamic objects to my own code using a custom `ValueGetter` and `ValueGetterFactory`.

```csharp
public class DynamicMetaObjectProviderValueGetter : ValueGetter
{
private readonly IDynamicMetaObjectProvider target;
private readonly string name;

internal DynamicMetaObjectProviderValueGetter(IDynamicMetaObjectProvider target, string name)
{
this.target = target;
this.name = name;
}

public override object GetValue()
{
var binder = Binder.GetMember(CSharpBinderFlags.None, name, target.GetType(), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });

var callsite = CallSite>.Create(binder);
return callsite.Target(callsite, target);
}
}

public class DynamicMetaObjectProviderValueGetterFactory : ValueGetterFactory
{
public override ValueGetter GetValueGetter(object target, string name)
{
var provider = target as IDynamicMetaObjectProvider;
if (provider != null)
{
var members = provider.GetMetaObject(Expression.Constant(target)).GetDynamicMemberNames();
string result = members.FirstOrDefault(x => string.Equals(x, name, DefaultNameComparison));

if (result != null)
{
return new DynamicMetaObjectProviderValueGetter(provider, result);
}
}

return null;
}
}
```

The following tests should now pass.

``` csharp
[TestFixture]
public class DynamicMetaObjectProviderValueGetterTests
{
public class ReadWriteDynamicObject : DynamicObject
{
private readonly Dictionary obj = new Dictionary();

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return obj.TryGetValue(binder.Name, out result);
}

public override bool TrySetMember(SetMemberBinder binder, object value)
{
obj[binder.Name] = value;
return true;
}

public override IEnumerable GetDynamicMemberNames()
{
return obj.Keys;
}
}

[TestFixtureSetUp]
public void Context()
{
ValueGetterFactories.Factories.Add(new DynamicMetaObjectProviderValueGetterFactory());
}

[Test]
public void It_gets_dynamic_members()
{
dynamic target = new ReadWriteDynamicObject();
target.DynamicMember = 123;

Assert.AreEqual(123, ValueGetter.GetValue(target, "DynamicMember"));
}

[Test]
public void It_gets_case_insensitive_dynamic_members()
{
dynamic target = new ReadWriteDynamicObject();
target.DynamicMember = 123;

Assert.AreEqual(123, ValueGetter.GetValue(target, "dynamicMember"));
}
}
```