diff --git a/Slapper.AutoMapper.Tests/Slapper.Tests.csproj b/Slapper.AutoMapper.Tests/Slapper.Tests.csproj index b8c1172..d72260c 100644 --- a/Slapper.AutoMapper.Tests/Slapper.Tests.csproj +++ b/Slapper.AutoMapper.Tests/Slapper.Tests.csproj @@ -66,6 +66,7 @@ + diff --git a/Slapper.AutoMapper.Tests/TypeActivatorTests.cs b/Slapper.AutoMapper.Tests/TypeActivatorTests.cs new file mode 100644 index 0000000..5abf306 --- /dev/null +++ b/Slapper.AutoMapper.Tests/TypeActivatorTests.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace Slapper.Tests +{ + public class TypeActivatorTests + { + public interface IClass + { + string Id { get; set; } + + INestedClass Nested { get; set; } + } + + public class Class : IClass + { + public string Id { get; set; } + + public INestedClass Nested { get; set; } + } + + public interface INestedClass + { + int Count { get; set; } + + Enum Enum { get; set; } + } + + public class NestedClass : INestedClass + { + public int Count { get; set; } + + public Enum Enum { get; set; } + } + + public enum Enum + { + One, + Two + } + + public class TypeActivator : AutoMapper.Configuration.ITypeActivator + { + private readonly IDictionary supportedTypes = new Dictionary + { + { typeof(IClass), typeof(Class) }, + { typeof(INestedClass), typeof(NestedClass) } + }; + + public object Create(Type type) + { + var concreteType = this.supportedTypes[type]; + return Activator.CreateInstance(concreteType); + } + + public bool CanCreate(Type type) + { + return this.supportedTypes.ContainsKey(type); + } + + public int Order { get; } = 1; + } + + [Test] + public void Can_Use_Registered_TypeActivators_WithInterfaces() + { + // Arrange + const string id = "1"; + const int count = 2; + const Enum enu = Enum.Two; + + var dictionary = new Dictionary + { + { "Id", id }, + { "Nested_Count", count }, + { "Nested_Enum", enu } + }; + + AutoMapper.Configuration.TypeActivators.Add(new TypeActivator()); + + // Act + var result = Slapper.AutoMapper.Map(dictionary); + + // Assert + Assert.NotNull(result); + Assert.That(result.Id == id); + Assert.That(result.Nested.Enum == enu); + Assert.That(result.Nested.Count == count); + } + + public interface IClass2 + { + string Id { get; set; } + } + + public class Class2 : IClass2 + { + public string Id { get; set; } + } + + public class Class2Copy : IClass2 + { + public string Id { get; set; } + } + + public class Class2TypeActivator : AutoMapper.Configuration.ITypeActivator + { + public object Create(Type type) + { + return new Class2(); + } + + public bool CanCreate(Type type) + { + return type.IsAssignableFrom(typeof(IClass2)); + } + + public int Order { get; } = 2; + } + + public class Class2CopyTypeActivator : AutoMapper.Configuration.ITypeActivator + { + public object Create(Type type) + { + return new Class2Copy(); + } + + public bool CanCreate(Type type) + { + return type.IsAssignableFrom(typeof(IClass2)); + } + + public int Order { get; } = 1; + } + + [Test] + public void TypeActivator_Order_Is_Adhered_To() + { + // Arrange + const string id = "1"; + const int count = 2; + const Enum enu = Enum.Two; + + var dictionary = new Dictionary + { + { "Id", id }, + { "Nested_Count", count }, + { "Nested_Enum", enu } + }; + + AutoMapper.Configuration.TypeActivators.Add(new Class2TypeActivator()); + AutoMapper.Configuration.TypeActivators.Add(new Class2CopyTypeActivator()); + + // Act + var result = Slapper.AutoMapper.Map(dictionary); + + // Assert + Assert.NotNull(result); + Assert.That(result.Id == id); + Assert.That(result.GetType() == typeof(Class2Copy)); + } + } +} diff --git a/Slapper.AutoMapper/Slapper.AutoMapper.Configuration.cs b/Slapper.AutoMapper/Slapper.AutoMapper.Configuration.cs index 6c951a5..405c420 100644 --- a/Slapper.AutoMapper/Slapper.AutoMapper.Configuration.cs +++ b/Slapper.AutoMapper/Slapper.AutoMapper.Configuration.cs @@ -78,6 +78,11 @@ static Configuration() /// public static readonly List TypeConverters = new List(); + /// + /// Activators to instantiate types. + /// + public static readonly List TypeActivators = new List(); + /// /// Applies default conventions for finding identifiers. /// @@ -281,6 +286,31 @@ public bool CanConvert(object value, Type type) #endregion } + + /// + /// Defines an interface for an activator for a specific type. + /// + public interface ITypeActivator + { + /// + /// Creates the type. + /// + /// The type to create. + /// The created type. + object Create(Type type); + + /// + /// Indicates whether it can create the type. + /// + /// The type to create. + /// Boolean response. + bool CanCreate(Type type); + + /// + /// The order to try the activator in. + /// + int Order { get; } + } } #endregion Configuration diff --git a/Slapper.AutoMapper/Slapper.AutoMapper.InternalHelpers.cs b/Slapper.AutoMapper/Slapper.AutoMapper.InternalHelpers.cs index aebb965..54f7a0f 100644 --- a/Slapper.AutoMapper/Slapper.AutoMapper.InternalHelpers.cs +++ b/Slapper.AutoMapper/Slapper.AutoMapper.InternalHelpers.cs @@ -88,7 +88,23 @@ public static Dictionary GetFieldsAndProperties(Type type) /// public static object CreateInstance(Type type) { - return type == typeof(string) ? string.Empty : Activator.CreateInstance(type); + if (type == typeof(string)) + { + return string.Empty; + } + + if (Configuration.TypeActivators.Count > 0) + { + foreach (var typeActivator in Configuration.TypeActivators.OrderBy(ta => ta.Order)) + { + if (typeActivator.CanCreate(type)) + { + return typeActivator.Create(type); + } + } + } + + return Activator.CreateInstance(type); } /// @@ -466,8 +482,12 @@ internal static object Map(IDictionary dictionary, object instan // hasn't been initialized, then this will return null. object nestedInstance = GetMemberValue(member, instance); - // If the member is null and is a class, try to create an instance of the type - if (nestedInstance == null && memberType.IsClass) + var genericCollectionType = typeof(IEnumerable<>); + var isEnumerableType = memberType.IsGenericType && genericCollectionType.IsAssignableFrom(memberType.GetGenericTypeDefinition()) + || memberType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericCollectionType); + + // If the member is null and is a class or interface (not ienumerable), try to create an instance of the type + if (nestedInstance == null && (memberType.IsClass || (memberType.IsInterface && !isEnumerableType))) { if (memberType.IsArray) { @@ -475,14 +495,13 @@ internal static object Map(IDictionary dictionary, object instan } else { - nestedInstance = typeof(IEnumerable).IsAssignableFrom(memberType) ? CreateInstance(memberType) : GetInstance(memberType, newDictionary, parentInstance == null ? 0 : parentInstance.GetHashCode()).Item2; + nestedInstance = typeof(IEnumerable).IsAssignableFrom(memberType) + ? CreateInstance(memberType) + : GetInstance(memberType, newDictionary, parentInstance == null ? 0 : parentInstance.GetHashCode()).Item2; } } - Type genericCollectionType = typeof(IEnumerable<>); - - if (memberType.IsGenericType && genericCollectionType.IsAssignableFrom(memberType.GetGenericTypeDefinition()) - || memberType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericCollectionType)) + if (isEnumerableType) { var innerType = memberType.GetGenericArguments().FirstOrDefault() ?? memberType.GetElementType(); nestedInstance = MapCollection(innerType, newDictionary, nestedInstance, instance); diff --git a/Slapper.AutoMapper/Slapper.AutoMapper.cs b/Slapper.AutoMapper/Slapper.AutoMapper.cs index cbf1f71..e15c5c5 100644 --- a/Slapper.AutoMapper/Slapper.AutoMapper.cs +++ b/Slapper.AutoMapper/Slapper.AutoMapper.cs @@ -65,7 +65,7 @@ public class Id : Attribute #region Mapping /// - /// Converts a list of dynamic property names and values to a list of type of . + /// Converts a dynamic object to a type . /// /// Population of complex nested child properties is supported by underscoring "_" into the /// nested child properties in the property name. @@ -73,25 +73,43 @@ public class Id : Attribute /// Type to instantiate and automap to /// Dynamic list of property names and values /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. - /// List of type + /// The type /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. public static T MapDynamic( object dynamicObject, bool keepCache = true) { - if ( dynamicObject == null ) - return default( T ); + return (T)MapDynamic(typeof(T), dynamicObject, keepCache); + } + + /// + /// Converts a dynamic object to a specified Type. + /// + /// Population of complex nested child properties is supported by underscoring "_" into the + /// nested child properties in the property name. + /// + /// Type to instantiate and automap to + /// Dynamic list of property names and values + /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. + /// The specified Type + /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. + public static object MapDynamic(Type type, object dynamicObject, bool keepCache = true) + { + if (dynamicObject == null) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } var dictionary = dynamicObject as IDictionary; - if ( dictionary == null ) - throw new ArgumentException( "Object type cannot be converted to an IDictionary", "dynamicObject" ); + if (dictionary == null) + throw new ArgumentException("Object type cannot be converted to an IDictionary", "dynamicObject"); var propertiesList = new List> { dictionary }; - return Map( propertiesList, keepCache ).FirstOrDefault(); + return Map(type, propertiesList, keepCache).FirstOrDefault(); } /// - /// Converts a list of dynamic property names and values to a list of type of . + /// Converts a list of dynamic objects to a list of type of . /// /// Population of complex nested child properties is supported by underscoring "_" into the /// nested child properties in the property name. @@ -103,22 +121,38 @@ public static T MapDynamic( object dynamicObject, bool keepCache = true) /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. public static IEnumerable MapDynamic( IEnumerable dynamicListOfProperties, bool keepCache = true) { - if ( dynamicListOfProperties == null ) - return new List(); + return MapDynamic(typeof(T), dynamicListOfProperties, keepCache).Cast(); + } - var dictionary = dynamicListOfProperties.Select( dynamicItem => dynamicItem as IDictionary ).ToList(); + /// + /// Converts a list of dynamic objects to a list of specified Type. + /// + /// Population of complex nested child properties is supported by underscoring "_" into the + /// nested child properties in the property name. + /// + /// Type to instantiate and automap to + /// Dynamic list of property names and values + /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. + /// List of specified Type + /// Exception that is thrown when the cannot be converted to an IDictionary of type string and object. + public static IEnumerable MapDynamic(Type type, IEnumerable dynamicListOfProperties, bool keepCache = true) + { + if (dynamicListOfProperties == null) + return new List(); - if ( dictionary == null ) - throw new ArgumentException( "Object types cannot be converted to an IDictionary", "dynamicListOfProperties" ); + var dictionary = dynamicListOfProperties.Select(dynamicItem => dynamicItem as IDictionary).ToList(); - if ( dictionary.Count == 0 || dictionary[ 0 ] == null ) - return new List(); + if (dictionary == null) + throw new ArgumentException("Object types cannot be converted to an IDictionary", "dynamicListOfProperties"); - return Map( dictionary, keepCache ); + if (dictionary.Count == 0 || dictionary[0] == null) + return new List(); + + return Map(type, dictionary, keepCache); } /// - /// Converts a list of property names and values to a list of type of . + /// Converts a dictionary of property names and values to a type . /// /// Population of complex nested child properties is supported by underscoring "_" into the /// nested child properties in the property name. @@ -126,16 +160,31 @@ public static IEnumerable MapDynamic( IEnumerable dynamicListOfPro /// Type to instantiate and automap to /// List of property names and values /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. - /// List of type + /// The type public static T Map( IDictionary listOfProperties, bool keepCache = true) + { + return (T)Map(typeof(T), listOfProperties, keepCache); + } + + /// + /// Converts a dictionary of property names and values to a specified Type. + /// + /// Population of complex nested child properties is supported by underscoring "_" into the + /// nested child properties in the property name. + /// + /// Type to instantiate and automap to + /// List of property names and values + /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. + /// The specified Type + public static object Map(Type type, IDictionary listOfProperties, bool keepCache = true) { var propertiesList = new List> { listOfProperties }; - return Map( propertiesList, keepCache ).FirstOrDefault(); + return Map(type, propertiesList, keepCache).FirstOrDefault(); } /// - /// Converts a list of property names and values to a list of type of . + /// Converts a list of dictionaries of property names and values to a list of type of . /// /// Population of complex nested child properties is supported by underscoring "_" into the /// nested child properties in the property name. @@ -145,31 +194,46 @@ public static T Map( IDictionary listOfProperties, bool keepC /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. /// List of type public static IEnumerable Map( IEnumerable> listOfProperties, bool keepCache = true) + { + return Map(typeof(T), listOfProperties, keepCache).Cast(); + } + + /// + /// Converts a list of dictionaries of property names and values to a list of specified Type. + /// + /// Population of complex nested child properties is supported by underscoring "_" into the + /// nested child properties in the property name. + /// + /// Type to instantiate and automap to + /// List of property names and values + /// If false, clears instance cache after mapping is completed. Defaults to true, meaning instances are kept between calls. + /// List of specified Type + public static IEnumerable Map(Type type, IEnumerable> listOfProperties, bool keepCache = true) { var instanceCache = new Dictionary(); - foreach ( var properties in listOfProperties ) + foreach (var properties in listOfProperties) { - var getInstanceResult = InternalHelpers.GetInstance( typeof ( T ), properties); + var getInstanceResult = InternalHelpers.GetInstance(type, properties); object instance = getInstanceResult.Item2; var key = getInstanceResult.Item3; - if ( instanceCache.ContainsKey(key) == false ) + if (instanceCache.ContainsKey(key) == false) { - instanceCache.Add(key, instance ); + instanceCache.Add(key, instance); } - var caseInsensitiveDictionary = new Dictionary( properties, StringComparer.OrdinalIgnoreCase ); - + var caseInsensitiveDictionary = new Dictionary(properties, StringComparer.OrdinalIgnoreCase); + InternalHelpers.Map(caseInsensitiveDictionary, instance); } if (!keepCache) Cache.ClearInstanceCache(); - return instanceCache.Select(pair => ( T ) pair.Value); + return instanceCache.Select(pair => pair.Value); } #endregion Mapping