Skip to content

Instantly share code, notes, and snippets.

@cobysy
Last active April 20, 2016 11:09
Show Gist options
  • Save cobysy/1f7251e914f6ba28bed4966fdbac759d to your computer and use it in GitHub Desktop.
Save cobysy/1f7251e914f6ba28bed4966fdbac759d to your computer and use it in GitHub Desktop.
DynamicObject that is serializable
public class DTO : SerializableDynamicObject
{
[Required]
public DTOEventType EventType
{
get { return GetValue<DTOEventType>(nameof(EventType)); }
set { SetValue(nameof(EventType), value); }
}
}
/// <summary>
/// Based on https://loosexaml.wordpress.com/2011/01/01/wcf-serialization-of-dlr-dynamic-types/
/// </summary>
public class SerializableDynamicMetaObject : DynamicMetaObject
{
private readonly Func<IEnumerable<string>> _getMemberNames;
private readonly Type _objType;
//private readonly
/// <inheritdoc />
/// <param name="expression"></param>
/// <param name="value"></param>
/// <param name="getMemberNames">Delegate method to retrieve the list of member names.</param>
public SerializableDynamicMetaObject(Expression expression, object value, Func<IEnumerable<string>> getMemberNames)
: base(expression, BindingRestrictions.Empty, value)
{
_getMemberNames = getMemberNames;
_objType = value.GetType();
}
/// <inheritdoc />
public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
{
Expression self = Expression.Convert(Expression, _objType);
Expression property = Expression.Constant(binder.Name);
MethodInfo getMethod = _objType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(m => m.Name == "GetValue" && !m.IsGenericMethodDefinition);
Expression target = Expression.Call(self, getMethod, property);
return new DynamicMetaObject(target, BindingRestrictions.GetTypeRestriction(self, _objType));
}
/// <inheritdoc />
public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
{
Expression self = Expression.Convert(Expression, _objType);
Expression property = Expression.Constant(binder.Name);
Expression valueExpr = Expression.Convert(value.Expression, typeof (object));
MethodInfo setMethod = _objType.GetMethod("SetValue", BindingFlags.NonPublic | BindingFlags.Instance);
Expression target = Expression.Call(self, setMethod, property, valueExpr);
return new DynamicMetaObject(target, BindingRestrictions.GetTypeRestriction(self, _objType));
}
/// <inheritdoc />
public override IEnumerable<string> GetDynamicMemberNames() => _getMemberNames();
}
/// <summary>
/// Provides support serialization for DLR properties.
///
/// When serializing a DynamicObject-derived type with Newtonsoft.Json will result in empty json ("{}").
/// This is because the members and types are unknown to the serializer.
///
/// The workaround the creation of a DynamicMetaObject <see cref="SerializableDynamicMetaObject"/>,
/// which handles the evaluation of binding expressions.
/// </summary>
public class SerializableDynamicObject : IDynamicMetaObjectProvider
{
/// <summary>
/// Dictionary that stores extra members encountered during deserialization operations
/// and contains data that is not recognized as belonging to the data contract.
/// </summary>
private readonly IDictionary<string, object> _dynamicProperties = new Dictionary<string, object>();
/// <summary>
/// List of properties defined by declaration.
/// </summary>
private readonly IEnumerable<string> _declaredPropertyNames;
/// <summary>
///
/// </summary>
public SerializableDynamicObject()
{
_declaredPropertyNames = GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.SetProperty)
.Select(p => p.Name);
}
/// <inheritdoc />
DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression expression)
{
return new SerializableDynamicMetaObject(expression, this, GetMemberNames);
}
/// <inheritdoc />
protected object SetValue(string name, object value)
{
if (_declaredPropertyNames.Contains(name))
{
GetType().GetProperty(name).SetValue(this, value);
return value;
}
_dynamicProperties[name] = value;
return value;
}
/// <inheritdoc />
protected object GetValue(string name)
{
if (_declaredPropertyNames.Contains(name))
{
return GetType().GetProperty(name).GetValue(this);
}
object value;
_dynamicProperties.TryGetValue(name, out value);
return value;
}
/// <inheritdoc />
protected T GetValue<T>(string name)
{
var value = GetValue(name);
return value == null ? default(T) : (T) value;
}
/// <inheritdoc />
private IEnumerable<string> GetMemberNames()
{
IEnumerable<string> dynamicMemberNames = _dynamicProperties.Keys;
return _declaredPropertyNames.Concat(dynamicMemberNames);
}
}
[Fact]
public void CanSerializeToJson()
{
dynamic _request = new DTO
{
EventType = DTOEventType.Completed
};
_request.ExtraField = "xyz";
string json = JsonConvert.SerializeObject(_request);
json.Should().Contain(((int) DTOEventType.Completed).ToString());
json.Should().Contain("xyz");
string extraField = _request.ExtraField;
extraField.Should().Be("xyz");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment