Last active
April 20, 2016 11:09
-
-
Save cobysy/1f7251e914f6ba28bed4966fdbac759d to your computer and use it in GitHub Desktop.
DynamicObject that is serializable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DTO : SerializableDynamicObject | |
{ | |
[Required] | |
public DTOEventType EventType | |
{ | |
get { return GetValue<DTOEventType>(nameof(EventType)); } | |
set { SetValue(nameof(EventType), value); } | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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