Object to Object Mapper, Part 4

2/16/2010

In the last post I talked about the Mapping class and showed a number of functions that allowed for type conversion, data formatting, etc. The end result is a very simple, yet powerful enough object to object mapper that it can be used in a number of situations. However, getting back to my original post, it doesn't fit my situation. Therefore what I needed was something that would allow an assembly to specify what properties within an object to map and, based on their type, the application would specify how it would like to receive that data so that the user can manipulate it. At the same time neither the assembly nor the application would know what they are getting from each other. In my first post I showed a very basic attempt to accomplish this, here I will show you the final implementation:

   1: /// <summary>
   2: /// Factory mapping
   3: /// </summary>
   4: /// <typeparam name="Source">Source type</typeparam>
   5: public class FactoryMapping<Source>:IMapping
   6: {
   7:     #region Constructor
   8:  
   9:     /// <summary>
  10:     /// Constructor
  11:     /// </summary>
  12:     public FactoryMapping()
  13:     {
  14:         Setup();
  15:     }
  16:  
  17:     #endregion
  18:  
  19:     #region IMapping Members
  20:  
  21:     public Type SourceType
  22:     {
  23:         get { return typeof(Source); }
  24:     }
  25:  
  26:     public Type DestinationType
  27:     {
  28:         get { return typeof(List<object>); }
  29:     }
  30:  
  31:     public void Sync(object Source, object Destination)
  32:     {
  33:         List<object> TempDestination = (List<object>)Destination;
  34:         int x = 0;
  35:         foreach (MappingInfo PropertyMapping in PropertyMappings)
  36:         {
  37:             if (!string.IsNullOrEmpty(PropertyMapping.Description))
  38:             {
  39:                 ++x;
  40:             }
  41:             PropertyInfo PropertyInfo = null;
  42:             object TempSourceObject = Utilities.Reflection.GetPropertyParent(Source, PropertyMapping.Source, out PropertyInfo);
  43:             SourceMapping.Sync(TempSourceObject, PropertyInfo, TempDestination[x]);
  44:             ++x;
  45:         }
  46:     }
  47:  
  48:     public object Create(object Source)
  49:     {
  50:         List<object> ReturnList = new List<object>();
  51:         foreach (MappingInfo PropertyMapping in PropertyMappings)
  52:         {
  53:             if (!string.IsNullOrEmpty(PropertyMapping.Description))
  54:             {
  55:                 ReturnList.Add(SourceMapping.CreateDescription(PropertyMapping.Description));
  56:             }
  57:             ReturnList.Add(SourceMapping.CreateDestination(Utilities.Reflection.GetPropertyValue(Source, PropertyMapping.Source),
  58:                 Utilities.Reflection.GetPropertyType(Source,PropertyMapping.Source)));
  59:         }
  60:         return ReturnList;
  61:     }
  62:  
  63:     #endregion
  64:  
  65:     #region Protected Functions
  66:  
  67:     /// <summary>
  68:     /// Maps source property
  69:     /// </summary>
  70:     /// <param name="SourceExpression">Source property to map</param>
  71:     protected void Map(Expression<Func<Source, object>> SourceExpression)
  72:     {
  73:         Map(SourceExpression, "");
  74:     }
  75:  
  76:     /// <summary>
  77:     /// Maps source property
  78:     /// </summary>
  79:     /// <param name="SourceExpression">Source property to map</param>
  80:     /// <param name="Format">Format string</param>
  81:     protected void Map(Expression<Func<Source, object>> SourceExpression, string Format)
  82:     {
  83:         Setup();
  84:         string SourceName = Utilities.Reflection.GetPropertyName<Source>(SourceExpression);
  85:         PropertyInfo SourceProperty = Utilities.Reflection.GetProperty<Source>(SourceName);
  86:         object[] Attributes = SourceProperty.GetCustomAttributes(typeof(Description), true);
  87:         if (Attributes.Length > 0)
  88:         {
  89:             PropertyMappings.Add(new MappingInfo(SourceName, "", Format, ((Description)Attributes[0]).DescriptionValue));
  90:         }
  91:         else
  92:         {
  93:             PropertyMappings.Add(new MappingInfo(SourceName, "", Format, ""));
  94:         }
  95:     }
  96:  
  97:     #endregion
  98:  
  99:     #region Private Functions
 100:  
 101:     /// <summary>
 102:     /// Sets up the mappings
 103:     /// </summary>
 104:     private void Setup()
 105:     {
 106:         if (PropertyMappings != null)
 107:             return;
 108:         SourceMapping = (TypeMapping)Manager.Instance.TypeMapping;
 109:         PropertyMappings = new List<MappingInfo>();
 110:     }
 111:  
 112:     #endregion
 113:  
 114:     #region Internal Properties
 115:  
 116:     internal TypeMapping SourceMapping { get; set; }
 117:     internal List<MappingInfo> PropertyMappings { get; set; }
 118:  
 119:     #endregion
 120: }

The code above is actually fairly similar to the Mapping class that I've already shown. We have our Map, Sync, and Create functions. However the mapping is one sided in this case. You'll notice that the only thing that is mapped is the source type and property. In both the Sync and Create functions you'll notice that it takes the mapped source info and feeds it to a TypeMapping class:

   1: /// <summary>
   2: /// Maps a type to another type
   3: /// </summary>
   4: public class TypeMapping : Factory<Type, object>, ITypeMapping
   5: {
   6:     #region Constructor
   7:  
   8:     /// <summary>
   9:     /// Constructor
  10:     /// </summary>
  11:     public TypeMapping()
  12:     {
  13:         Setup();
  14:     }
  15:  
  16:     #endregion
  17:  
  18:     #region Private Functions
  19:  
  20:     /// <summary>
  21:     /// Sets up the mappings
  22:     /// </summary>
  23:     private void Setup()
  24:     {
  25:         if (PropertyMappings != null)
  26:             return;
  27:         PropertyMappings = new Dictionary<Type, MappingInfo>();
  28:     }
  29:  
  30:     #endregion
  31:  
  32:     #region Protected Functions
  33:  
  34:     /// <summary>
  35:     /// Maps a type to another type
  36:     /// </summary>
  37:     /// <typeparam name="Source">Source type</typeparam>
  38:     /// <typeparam name="Destination">Destination type</typeparam>
  39:     /// <param name="DestinationType">Function for creating the destination type</param>
  40:     /// <param name="DestinationProperty">Property to map to</param>
  41:     protected void Map<Source, Destination>(Func<object> DestinationType,
  42:         Expression<Func<Destination, object>> DestinationProperty)
  43:     {
  44:         Setup();
  45:         Register(typeof(Source), DestinationType);
  46:         PropertyMappings.Add(typeof(Source), new MappingInfo("", Utilities.Reflection.GetPropertyName<Destination>(DestinationProperty), "",""));
  47:     }
  48:  
  49:     /// <summary>
  50:     /// Maps a type to another type
  51:     /// </summary>
  52:     /// <typeparam name="Source">Source type</typeparam>
  53:     /// <typeparam name="Destination">Destination type</typeparam>
  54:     /// <param name="DestinationType">Function for creating the destination type</param>
  55:     /// <param name="DestinationProperty">Property to map to</param>
  56:     /// <param name="Format">Format string</param>
  57:     protected void Map<Source, Destination>(Func<object> DestinationType,
  58:         Expression<Func<Destination, object>> DestinationProperty,
  59:         string Format)
  60:     {
  61:         Setup();
  62:         Register(typeof(Source), DestinationType);
  63:         PropertyMappings.Add(typeof(Source), new MappingInfo("", Utilities.Reflection.GetPropertyName<Destination>(DestinationProperty), Format, ""));
  64:     }
  65:  
  66:     /// <summary>
  67:     /// Maps a description to another type
  68:     /// </summary>
  69:     /// <typeparam name="Destination">Destination type</typeparam>
  70:     /// <param name="DestinationType">Function for creating the destination type</param>
  71:     /// <param name="DestinationProperty">Property to map to</param>
  72:     protected void MapDescription<Destination>(Func<object> DestinationType,
  73:         Expression<Func<Destination, object>> DestinationProperty)
  74:     {
  75:         MapDescription<Destination>(DestinationType, DestinationProperty, "");
  76:     }
  77:  
  78:     /// <summary>
  79:     /// Maps a description to another type
  80:     /// </summary>
  81:     /// <typeparam name="Destination">Destination type</typeparam>
  82:     /// <param name="DestinationType">Function for creating the destination type</param>
  83:     /// <param name="DestinationProperty">Property to map to</param>
  84:     /// <param name="Format">Formatting string</param>
  85:     protected void MapDescription<Destination>(Func<object> DestinationType,
  86:         Expression<Func<Destination, object>> DestinationProperty,
  87:         string Format)
  88:     {
  89:         Register(typeof(MappingInfo), DestinationType);
  90:         DescriptionMapping = new MappingInfo("", Utilities.Reflection.GetPropertyName<Destination>(DestinationProperty), Format, "");
  91:     }
  92:  
  93:     #endregion
  94:  
  95:     #region Public Functions
  96:  
  97:     /// <summary>
  98:     /// Creates the destination type
  99:     /// </summary>
 100:     /// <param name="SourceValue">Source value</param>
 101:     /// <param name="SourceType">Source type</param>
 102:     /// <returns>The destination type</returns>
 103:     public object CreateDestination(object SourceValue, Type SourceType)
 104:     {
 105:         object DestinationObject = Create(SourceType);
 106:         Utilities.Reflection.SetValue(SourceValue, DestinationObject, PropertyMappings[SourceType].Destination, PropertyMappings[SourceType].Format);
 107:         return DestinationObject;
 108:     }
 109:  
 110:     /// <summary>
 111:     /// Creates a description object based off of the description input
 112:     /// </summary>
 113:     /// <param name="DescriptionValue">The description value</param>
 114:     /// <returns>The description object</returns>
 115:     public object CreateDescription(object DescriptionValue)
 116:     {
 117:         object DestinationObject = Create(typeof(MappingInfo));
 118:         Utilities.Reflection.SetValue(DescriptionValue, DestinationObject, DescriptionMapping.Destination, DescriptionMapping.Format);
 119:         return DestinationObject;
 120:     }
 121:  
 122:     /// <summary>
 123:     /// Syncs the source to the destination
 124:     /// </summary>
 125:     /// <param name="SourceObject">Source object</param>
 126:     /// <param name="SourcePropertyInfo">Property to copy</param>
 127:     /// <param name="DestinationObject">Destination object</param>
 128:     public void Sync(object SourceObject, PropertyInfo SourcePropertyInfo, object DestinationObject)
 129:     {
 130:         object Value = Utilities.Reflection.GetPropertyValue(DestinationObject, PropertyMappings[SourcePropertyInfo.PropertyType].Destination);
 131:         Utilities.Reflection.SetValue(Value, SourceObject, SourcePropertyInfo, PropertyMappings[SourcePropertyInfo.PropertyType].Format);
 132:     }
 133:  
 134:     #endregion
 135:  
 136:     #region Internal Properties
 137:  
 138:     internal Dictionary<Type, MappingInfo> PropertyMappings { get; set; }
 139:     internal MappingInfo DescriptionMapping { get; set; }
 140:  
 141:     #endregion
 142: }

The TypeMapping class acts in sort of reverse. It maps a data type to a destination type/property. So for instance you could map a string to a TextBox's Text property. The Map functions are slightly different as it wants a function that will create an object of the destination type (so you would feed it something like new Func<object>(() => new TextBox())). Anyway, the Sync and Create functions on the other hand simply take the data from the FactoryMapping and spits out the appropriate object. In essence it's very similar to the Mapping class but just splits the operations across two classes.

The benefit of doing it this way is that the manager doesn't need to change much. The only change is it needs to search for the ITypeMapping interface as well and save it in a property (TypeMapping). The down side though is the fact that the Syncing is one way. Having the generic TypeMapping class means that I never know what the type of the source is and therefore can't create one or sync to it. Thus every time you want to go from the source to the destination you must create the objects again. Otherwise it works perfectly well.

So we're good right? We have a way to get the data from the object in a format that the user can modify. Well, not really. We have the data but all the user is going to see is a textbox with no idea what to put into it. As such we need one last thing. You may have noticed that in the FactoryMapping class's Map function it's looking for a specific attribute (and specifically the DescriptionValue property of said attribute). It then feeds it to the TypeMapping using the CreateDescription function. This function in turn uses a specially designated MappingInfo class (DescriptionMapping), which is generated through the use of MapDescription. You see the reason for all of this was that I needed a way for the assembly to describe what the data was in such a way that the end user would know what they are modifying. As such, the business object would add this attribute:

   1: [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
   2: public class Description:Attribute
   3: {
   4:     #region Constructor
   5:  
   6:     /// <summary>
   7:     /// Constructor
   8:     /// </summary>
   9:     /// <param name="Value">Description of the property</param>
  10:     public Description(string Value)
  11:     {
  12:         DescriptionValue = Value;
  13:     }
  14:  
  15:     #endregion
  16:  
  17:     #region Public Properties
  18:  
  19:     /// <summary>
  20:     /// Description value
  21:     /// </summary>
  22:     public string DescriptionValue { get; set; }
  23:  
  24:     #endregion
  25: }

This is added to any property that they would like to map like this:

   1: [Description("Text Field Description: ")]
   2: public string Text { get; set; }

And in turn the application would specify that it wants any description to be a specific type (for instance a Label). Thus the app would get a list of objects that would contain the description as well as the object. So there we go, we have the assembly on one side supplying our objects and the app on the other showing the info to the user and neither one needs to know anything about the other.

On a side note, the code is now compiled and put into a nice little project on Codeplex: http://objectcartographer.codeplex.com/. So if you are interested in downloading the final code base, go there to check it out.



Comments