diff --git a/src/Umbraco.Core/Mapping/Mapper.cs b/src/Umbraco.Core/Mapping/Mapper.cs
index 377575fed6..5c6b5b76d8 100644
--- a/src/Umbraco.Core/Mapping/Mapper.cs
+++ b/src/Umbraco.Core/Mapping/Mapper.cs
@@ -5,8 +5,13 @@ using System.Linq;
namespace Umbraco.Core.Mapping
{
- // FIXME needs documentation and cleanup!
+ // notes:
+ // AutoMapper maps null to empty arrays, lists, etc
+ // AutoMapper maps derived types - we have to be explicit (see DefineAs) - fixme / really?
+ ///
+ /// Umbraco Mapper.
+ ///
public class Mapper
{
private readonly Dictionary>> _ctors
@@ -15,6 +20,10 @@ namespace Umbraco.Core.Mapping
private readonly Dictionary>> _maps
= new Dictionary>>();
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
public Mapper(MapperProfileCollection profiles)
{
foreach (var profile in profiles)
@@ -23,14 +32,85 @@ namespace Umbraco.Core.Mapping
#region Define
+ private TTarget ThrowCtor(TSource source, MapperContext context)
+ => throw new InvalidOperationException($"Don't know how to create {typeof(TTarget).FullName} instances.");
+
+ private void Identity(TSource source, TTarget target, MapperContext context)
+ { }
+
+ ///
+ /// Defines a mapping.
+ ///
+ /// The source type.
+ /// The target type.
public void Define()
- => Define((source, target, context) => { });
+ => Define(ThrowCtor, Identity);
+ ///
+ /// Defines a mapping.
+ ///
+ /// The source type.
+ /// The target type.
+ /// A mapping method.
public void Define(Action map)
- => Define((source, context) => throw new NotSupportedException($"Don't know how to create {typeof(TTarget)} instances."), map);
+ => Define(ThrowCtor, map);
+ ///
+ /// Defines a mapping.
+ ///
+ /// The source type.
+ /// The target type.
+ /// A constructor method.
public void Define(Func ctor)
- => Define(ctor, (source, target, context) => { });
+ => Define(ctor, Identity);
+
+ ///
+ /// Defines a mapping.
+ ///
+ /// The source type.
+ /// The target type.
+ /// A constructor method.
+ /// A mapping method.
+ public void Define(Func ctor, Action map)
+ {
+ var sourceType = typeof(TSource);
+ var targetType = typeof(TTarget);
+
+ var sourceCtors = DefineCtors(sourceType);
+ if (ctor != null)
+ sourceCtors[targetType] = (source, context) => ctor((TSource)source, context);
+
+ var sourceMaps = DefineMaps(sourceType);
+ sourceMaps[targetType] = (source, target, context) => map((TSource)source, (TTarget)target, context);
+ }
+
+ ///
+ /// Defines a mapping as a clone of an already defined mapping.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The equivalent source type.
+ /// The equivalent target type.
+ public void DefineAs()
+ {
+ var sourceType = typeof(TSource);
+ var targetType = typeof(TTarget);
+
+ var asSourceType = typeof(TAsSource);
+ var asTargetType = typeof(TAsTarget);
+
+ var asCtors = DefineCtors(asSourceType);
+ var asMaps = DefineMaps(asSourceType);
+
+ var sourceCtors = DefineCtors(sourceType);
+ var sourceMaps = DefineMaps(sourceType);
+
+ if (!asCtors.TryGetValue(asTargetType, out var ctor) || !asMaps.TryGetValue(asTargetType, out var map))
+ throw new InvalidOperationException($"Don't know hwo to map from {asSourceType.FullName} to {targetType.FullName}.");
+
+ sourceCtors[targetType] = ctor;
+ sourceMaps[targetType] = map;
+ }
private Dictionary> DefineCtors(Type sourceType)
{
@@ -46,25 +126,26 @@ namespace Umbraco.Core.Mapping
return sourceMap;
}
- public void Define(Func ctor, Action map)
- {
- var sourceType = typeof(TSource);
- var targetType = typeof(TTarget);
-
- var sourceCtors = DefineCtors(sourceType);
- sourceCtors[targetType] = (source, context) => ctor((TSource)source, context);
-
- var sourceMaps = DefineMaps(sourceType);
- sourceMaps[targetType] = (source, target, context) => map((TSource)source, (TTarget)target, context);
- }
-
#endregion
#region Map
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The target type.
+ /// The source object.
+ /// The target object.
public TTarget Map(object source)
=> Map(source, new MapperContext(this));
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The target type.
+ /// The source object.
+ /// A mapper context preparation method.
+ /// The target object.
public TTarget Map(object source, Action f)
{
var context = new MapperContext(this);
@@ -72,16 +153,63 @@ namespace Umbraco.Core.Mapping
return Map(source, context);
}
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The target type.
+ /// The source object.
+ /// A mapper context.
+ /// The target object.
public TTarget Map(object source, MapperContext context)
+ => Map(source, source?.GetType(), context);
+
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ public TTarget Map(TSource source)
+ => Map(source, new MapperContext(this));
+
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// A mapper context preparation method.
+ /// The target object.
+ public TTarget Map(TSource source, Action f)
+ {
+ var context = new MapperContext(this);
+ f(context);
+ return Map(source, context);
+ }
+
+ ///
+ /// Maps a source object to a new target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// A mapper context.
+ /// The target object.
+ public TTarget Map(TSource source, MapperContext context)
+ => Map(source, typeof(TSource), context);
+
+ private TTarget Map(object source, Type sourceType, MapperContext context)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
- var sourceType = source.GetType();
var targetType = typeof(TTarget);
- var ctor = GetCtor(sourceType, typeof(TTarget));
- var map = GetMap(sourceType, typeof(TTarget));
+ var ctor = GetCtor(sourceType, targetType);
+ var map = GetMap(sourceType, targetType);
+
+ // if there is a direct constructor, map
if (ctor != null && map != null)
{
var target = ctor(source, context);
@@ -89,36 +217,22 @@ namespace Umbraco.Core.Mapping
return (TTarget)target;
}
- bool IsOk(Type type)
+ // else, handle enumerable-to-enumerable mapping
+ if (IsGenericOrArray(sourceType) && IsGenericOrArray(targetType))
{
- // note: we're not going to work with just plain enumerables of anything,
- // only on arrays or anything that is generic and implements IEnumerable<>
-
- if (type.IsArray && type.GetArrayRank() == 1) return true;
- if (type.IsGenericType && type.GenericTypeArguments.Length == 1) return true;
- return false;
- }
-
- Type GetGenericArg(Type type)
- {
- if (type.IsArray) return type.GetElementType();
- if (type.IsGenericType) return type.GenericTypeArguments[0];
- throw new Exception("panic");
- }
-
- if (IsOk(sourceType) && IsOk(targetType))
- {
- var sourceGenericArg = GetGenericArg(sourceType);
- var targetGenericArg = GetGenericArg(targetType);
+ var sourceGenericArg = GetGenericOrArrayArg(sourceType);
+ var targetGenericArg = GetGenericOrArrayArg(targetType);
var sourceEnumerableType = typeof(IEnumerable<>).MakeGenericType(sourceGenericArg);
var targetEnumerableType = typeof(IEnumerable<>).MakeGenericType(targetGenericArg);
+ // if both are ienumerable
if (sourceEnumerableType.IsAssignableFrom(sourceType) && targetEnumerableType.IsAssignableFrom(targetType))
{
ctor = GetCtor(sourceGenericArg, targetGenericArg);
map = GetMap(sourceGenericArg, targetGenericArg);
+ // if there is a constructor for the underlying type, map
if (ctor != null && map != null)
{
var sourceEnumerable = (IEnumerable)source;
@@ -139,72 +253,26 @@ namespace Umbraco.Core.Mapping
throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}.");
}
- // TODO: when AutoMapper is completely gone these two methods can merge
-
- public TTarget Map(TSource source)
- => Map(source, new MapperContext(this));
-
- public TTarget Map(TSource source, Action f)
- {
- var context = new MapperContext(this);
- f(context);
- return Map(source, context);
- }
-
- public TTarget Map(TSource source, MapperContext context)
- {
- if (source == null)
- throw new ArgumentNullException(nameof(source));
-
- var sourceType = typeof(TSource);
- var targetType = typeof(TTarget);
-
- var ctor = GetCtor(sourceType, typeof(TTarget));
- var map = GetMap(sourceType, targetType);
- if (ctor != null && map != null)
- {
- var target = ctor(source, context);
- map(source, target, context);
- return (TTarget) target;
- }
-
- if (sourceType.IsGenericType && targetType.IsGenericType)
- {
- var sourceGeneric = sourceType.GetGenericTypeDefinition();
- var targetGeneric = targetType.GetGenericTypeDefinition();
- var ienumerable = typeof(IEnumerable<>);
-
- if (sourceGeneric == ienumerable && targetGeneric == ienumerable)
- {
- var sourceGenericType = sourceType.GenericTypeArguments[0];
- var targetGenericType = targetType.GenericTypeArguments[0];
-
- ctor = GetCtor(sourceGenericType, targetGenericType);
- map = GetMap(sourceGenericType, targetGenericType);
-
- if (ctor != null && map != null)
- {
- var sourceEnumerable = (IEnumerable)source;
- var targetEnumerable = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(targetGenericType));
-
- foreach (var sourceItem in sourceEnumerable)
- {
- var targetItem = ctor(sourceItem, context);
- map(sourceItem, targetItem, context);
- targetEnumerable.Add(targetItem);
- }
-
- return (TTarget)targetEnumerable;
- }
- }
- }
-
- throw new InvalidOperationException($"Don't know how to map {sourceType.FullName} to {targetType.FullName}.");
- }
-
+ ///
+ /// Maps a source object to an existing target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ /// The target object.
public TTarget Map(TSource source, TTarget target)
=> Map(source, target, new MapperContext(this));
+ ///
+ /// Maps a source object to an existing target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ /// A mapper context preparation method.
+ /// The target object.
public TTarget Map(TSource source, TTarget target, Action f)
{
var context = new MapperContext(this);
@@ -212,18 +280,32 @@ namespace Umbraco.Core.Mapping
return Map(source, target, context);
}
+ ///
+ /// Maps a source object to an existing target object.
+ ///
+ /// The source type.
+ /// The target type.
+ /// The source object.
+ /// The target object.
+ /// A mapper context.
+ /// The target object.
public TTarget Map(TSource source, TTarget target, MapperContext context)
{
- // fixme should we deal with enumerables?
+ var sourceType = typeof(TSource);
+ var targetType = typeof(TTarget);
- var map = GetMap(source.GetType(), typeof(TTarget));
- if (map == null)
+ var map = GetMap(sourceType, targetType);
+
+ // if there is a direct map, map
+ if (map != null)
{
- throw new InvalidOperationException($"Don't know how to map {typeof(TSource).FullName} to {typeof(TTarget).FullName}.");
+ map(source, target, context);
+ return target;
}
- map(source, target, context);
- return target;
+ // we cannot really map to an existing enumerable - give up
+
+ throw new InvalidOperationException($"Don't know how to map {typeof(TSource).FullName} to {typeof(TTarget).FullName}.");
}
private Func