Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Slapper.AutoMapper.Tests/Slapper.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
<Compile Include="SimpleTypeConversionTests.cs" />
<Compile Include="TestBase.cs" />
<Compile Include="TestHelpers.cs" />
<Compile Include="TypeActivatorTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Slapper.AutoMapper\Slapper.csproj">
Expand Down
164 changes: 164 additions & 0 deletions Slapper.AutoMapper.Tests/TypeActivatorTests.cs
Original file line number Diff line number Diff line change
@@ -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<Type, Type> supportedTypes = new Dictionary<Type, Type>
{
{ 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<string, object>
{
{ "Id", id },
{ "Nested_Count", count },
{ "Nested_Enum", enu }
};

AutoMapper.Configuration.TypeActivators.Add(new TypeActivator());

// Act
var result = Slapper.AutoMapper.Map<IClass>(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<string, object>
{
{ "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<IClass2>(dictionary);

// Assert
Assert.NotNull(result);
Assert.That(result.Id == id);
Assert.That(result.GetType() == typeof(Class2Copy));
}
}
}
30 changes: 30 additions & 0 deletions Slapper.AutoMapper/Slapper.AutoMapper.Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ static Configuration()
/// </summary>
public static readonly List<ITypeConverter> TypeConverters = new List<ITypeConverter>();

/// <summary>
/// Activators to instantiate types.
/// </summary>
public static readonly List<ITypeActivator> TypeActivators = new List<ITypeActivator>();

/// <summary>
/// Applies default conventions for finding identifiers.
/// </summary>
Expand Down Expand Up @@ -281,6 +286,31 @@ public bool CanConvert(object value, Type type)

#endregion
}

/// <summary>
/// Defines an interface for an activator for a specific type.
/// </summary>
public interface ITypeActivator
{
/// <summary>
/// Creates the type.
/// </summary>
/// <param name="type">The type to create.</param>
/// <returns>The created type.</returns>
object Create(Type type);

/// <summary>
/// Indicates whether it can create the type.
/// </summary>
/// <param name="type">The type to create.</param>
/// <returns>Boolean response.</returns>
bool CanCreate(Type type);

/// <summary>
/// The order to try the activator in.
/// </summary>
int Order { get; }
}
}

#endregion Configuration
Expand Down
35 changes: 27 additions & 8 deletions Slapper.AutoMapper/Slapper.AutoMapper.InternalHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,23 @@ public static Dictionary<string, object> GetFieldsAndProperties(Type type)
/// </returns>
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);
}

/// <summary>
Expand Down Expand Up @@ -466,23 +482,26 @@ internal static object Map(IDictionary<string, object> 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)
{
nestedInstance = new ArrayList().ToArray(memberType.GetElementType());
}
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);
Expand Down
Loading