Reputation: 1096
Recently I had to use ExpandoObject in my application, So I want to know how could I use my old Mapper to also map from Dynamic ExpandoOnjects, because some how it does not map the fields, and properties from an Expando.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
public class Mapper
{
private static readonly Dictionary<KeyValuePair<Type, Type>, object> Maps = new Dictionary<KeyValuePair<Type, Type>, object>();
private static PropertyInfo[] _fromProperties;
private static PropertyInfo[] _toProperties;
private static FieldInfo[] _fromFields;
private static FieldInfo[] _toFields;
// Rules...
private static readonly Func<PropertyInfo, PropertyInfo, bool> MatchingProps = (t1, t2) => t1.Name == t2.Name && t1.PropertyType.Name == t2.PropertyType.Name;
private static readonly Func<FieldInfo, FieldInfo, bool> MatchingFields = (t1, t2) => t1.Name == t2.Name && t1.FieldType.Name == t2.FieldType.Name;
private static readonly Func<PropertyInfo, FieldInfo, bool> MatchingPropertyToField = (t1, t2) => t1.Name == t2.Name && t1.PropertyType.Name == t2.FieldType.Name;
private static readonly Func<FieldInfo, PropertyInfo, bool> MatchingFieldToProperty = (t1, t2) => t1.Name == t2.Name && t1.FieldType.Name == t2.PropertyType.Name;
public static void AddMap<TFrom, TTo>(Action<TFrom, TTo> map = null)
where TFrom : class
where TTo : class { Maps.Add(new KeyValuePair<Type, Type>(typeof(TFrom), typeof(TTo)), map); }
public static void Map<TFromType, TOType>(TFromType @from, TOType to)
{
var key = new KeyValuePair<Type, Type>(typeof(TFromType), typeof(TOType));
var map = (Action<TFromType, TOType>)Maps[key];
bool hasMapping = Maps.Any(x => x.Key.Equals(key));
if (!hasMapping)
throw new Exception(string.Format("No map defined for {0} => {1}", typeof(TFromType).Name, typeof(TOType).Name));
Type tFrom = typeof(TFromType);
Type tTo = typeof(TOType);
_fromProperties = tFrom.GetProperties();
_fromFields = tFrom.GetFields();
_toProperties = tTo.GetProperties();
_toFields = tTo.GetFields();
SyncProperties(@from, to);
SyncFields(@from, to);
if (!Equals(map, null))
map(@from, to);
}
private static void SyncProperties<TFromType, TOType>(TFromType objFrom, TOType objTo)
{
PropertyInfo[] fromProperties = _fromProperties;
PropertyInfo[] toProperties = _toProperties;
FieldInfo[] toFields = _toFields;
if (fromProperties != null && fromProperties.Any()) {
foreach (PropertyInfo fromProperty in fromProperties) {
if (toProperties.Any(x => x.Name == fromProperty.Name)) {
PropertyInfo destinationProperty = toProperties.FirstOrDefault(x => x.Name == fromProperty.Name);
if (MatchingProps(fromProperty, destinationProperty)) {
object val = fromProperty.GetValue(objFrom, null);
if (Equals(val, null)) continue;
if (!Equals(destinationProperty, null)) destinationProperty.SetValue(objTo, Convert.ChangeType(val, fromProperty.PropertyType), null);
}
}
if (toFields.Any(x => x.Name == fromProperty.Name)) {
FieldInfo destinationField = toFields.FirstOrDefault(x => x.Name == fromProperty.Name);
if (MatchingPropertyToField(fromProperty, destinationField)) {
object val = fromProperty.GetValue(objFrom, null);
if (Equals(val, null)) continue;
if (!Equals(destinationField, null)) destinationField.SetValue(objTo, val);
}
}
}
}
}
private static void SyncFields<TFromType, TOType>(TFromType objFrom, TOType objTo)
{
FieldInfo[] fromFields = _fromFields;
FieldInfo[] toFields = _toFields;
PropertyInfo[] toProperties = _toProperties;
if (fromFields != null && fromFields.Any()) {
foreach (FieldInfo fromField in fromFields) {
if (toFields.Any(x => x.Name == fromField.Name)) {
FieldInfo destinationField = toFields.FirstOrDefault(x => x.Name == fromField.Name);
if (MatchingFields(fromField, destinationField)) {
object val = fromField.GetValue(objFrom);
if (Equals(val, null)) continue;
if (!Equals(destinationField, null)) destinationField.SetValue(objTo, val);
}
}
if (toProperties.Any(x => x.Name == fromField.Name)) {
PropertyInfo destinationProperty = toProperties.FirstOrDefault(x => x.Name == fromField.Name);
if (MatchingFieldToProperty(fromField, destinationProperty)) {
object val = fromField.GetValue(objFrom);
if (Equals(val, null)) continue;
if (!Equals(destinationProperty, null)) destinationProperty.SetValue(objTo, val, null);
}
}
}
}
}
}
Usage:
static void Main()
{
dynamic o = new ExpandoObject();
o.Name = "Pouce";
o.Age = 42;
o.Rank = new Rank
{
Name = Ranks.Major
};
o.Guid = new Guid();
Soldier soldier = new Soldier();
Mapper.AddMap<ExpandoObject, Soldier>();
Mapper.Map(o, soldier);
Console.ReadLine();
}
public class Soldier
{
public string Name { get; set; }
public int Age { get; set; }
public Rank Rank { get; set; }
public Guid Guid { get; set; }
}
public class Rank
{
public Ranks Name { get; set; }
}
public enum Ranks
{
Private,
Specialist,
Corporal,
Sergeant,
Captain,
Major,
Colonel,
General
}
<object,
object>
mapping; However when provided with <ExpandoObject, object>
it does not map anything.Upvotes: 2
Views: 6881
Reputation: 7622
This is because the properties of ExpandoObject
are not real .NET properties. By using ExpandoObject
with dynamic
keyword you are able to give any arbitrary properties to the object and this is handled by the DLR
at runtime.
You cannot use the regular static type's methods GetProperties()
and GetFields()
on the dynamic instance of ExpandoObject
.
To extend your Mapper
to consume ExpandObject
you will have to consider it as a special case.
See my answer here it may help you.
EDIT: Reflection with ExpandoObject
is not difficult. However, you wont get a set of PropertyInfo
or FieldInfo
from it. You just get KeyValuePair<string, object>
. So, you may have to add an array of such KeyValuePair
's to store the info.
In your Map()
method, you can check for ExpandoObject
as special case:
if (tFrom == typeof(ExpandoObject)) {
_fromExpandoProperties = @from.Select(kvp => kvp).ToArray();
// where _fromExpandoProperties is of type KeyValuePair<string, object>[]
} else {
_fromProperties = tFrom.GetProperties();
}
To get the name and value of the property you can then use .Key
and .Value
instead of .PropertyType.Name
and .GetValue()
.
You will have to factor this specialization in all your code.
Upvotes: 2