Skip to content

Commit

Permalink
Merge pull request #2261 from jbogard/ValueTransformers
Browse files Browse the repository at this point in the history
Value transformers in global, profiles, type maps, and members
  • Loading branch information
jbogard committed Sep 5, 2017
2 parents 67ce1d0 + e54b53d commit 01a4010
Show file tree
Hide file tree
Showing 15 changed files with 376 additions and 17 deletions.
1 change: 1 addition & 0 deletions src/AutoMapper/Configuration/IProfileConfiguration.cs
Expand Up @@ -44,5 +44,6 @@ public interface IProfileConfiguration
INamingConvention DestinationMemberNamingConvention { get; }
IEnumerable<ITypeMapConfiguration> TypeMapConfigs { get; }
IEnumerable<ITypeMapConfiguration> OpenTypeMapConfigs { get; }
IEnumerable<ValueTransformerConfiguration> ValueTransformers { get; }
}
}
15 changes: 15 additions & 0 deletions src/AutoMapper/Configuration/MappingExpression.cs
Expand Up @@ -135,6 +135,7 @@ public class MappingExpression<TSource, TDestination> : IMappingExpression<TSour
private readonly List<IPropertyMapConfiguration> _memberConfigurations = new List<IPropertyMapConfiguration>();
private readonly List<SourceMappingExpression> _sourceMemberConfigurations = new List<SourceMappingExpression>();
private readonly List<CtorParamConfigurationExpression<TSource>> _ctorParamConfigurations = new List<CtorParamConfigurationExpression<TSource>>();
private readonly List<ValueTransformerConfiguration> _valueTransformers = new List<ValueTransformerConfiguration>();
private MappingExpression<TDestination, TSource> _reverseMap;
private Action<IMemberConfigurationExpression<TSource, TDestination, object>> _allMemberOptions;
private Func<MemberInfo, bool> _memberFilter;
Expand All @@ -157,6 +158,7 @@ public MappingExpression(MemberList memberList, Type sourceType, Type destinatio
public Type DestinationType => Types.DestinationType;
public bool IsOpenGeneric { get; }
public ITypeMapConfiguration ReverseTypeMap => _reverseMap;
public IList<ValueTransformerConfiguration> ValueTransformers => _valueTransformers;
protected List<Action<TypeMap>> TypeMapActions { get; } = new List<Action<TypeMap>>();

public IMappingExpression<TSource, TDestination> PreserveReferences()
Expand Down Expand Up @@ -530,6 +532,15 @@ private void CheckIsDerived(Type derivedType, Type baseType)

return this;
}

public IMappingExpression<TSource, TDestination> AddTransform<TValue>(Expression<Func<TValue, TValue>> transformer)
{
var config = new ValueTransformerConfiguration(typeof(TValue), transformer);

_valueTransformers.Add(config);

return this;
}

private IPropertyMapConfiguration GetDestinationMemberConfiguration(MemberInfo destinationMember) =>
_memberConfigurations.FirstOrDefault(m => m.DestinationMember == destinationMember);
Expand Down Expand Up @@ -578,6 +589,10 @@ public void Configure(TypeMap typeMap)
{
paramConfig.Configure(typeMap);
}
foreach (var valueTransformer in _valueTransformers)
{
typeMap.AddValueTransformation(valueTransformer);
}

if (_reverseMap != null)
{
Expand Down
10 changes: 10 additions & 0 deletions src/AutoMapper/Configuration/MemberConfigurationExpression.cs
Expand Up @@ -230,6 +230,16 @@ public void PreCondition(Func<TSource, ResolutionContext, bool> condition)
});
}

public void AddTransform(Expression<Func<TMember, TMember>> transformer)
{
PropertyMapActions.Add(pm =>
{
var config = new ValueTransformerConfiguration(typeof(TMember), transformer);
pm.AddValueTransformation(config);
});
}

public void ExplicitExpansion()
{
PropertyMapActions.Add(pm => pm.ExplicitExpansion = true);
Expand Down
10 changes: 8 additions & 2 deletions src/AutoMapper/Execution/TypeMapPlanBuilder.cs
Expand Up @@ -5,7 +5,7 @@ namespace AutoMapper.Execution
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper.Configuration;
using Configuration;
using static System.Linq.Expressions.Expression;
using static Internal.ExpressionFactory;
using static ExpressionBuilder;
Expand Down Expand Up @@ -388,6 +388,12 @@ private Expression CreatePropertyMapFunc(PropertyMap propertyMap, Expression des
propertyMap, destValueExpr)
: ContextMap(typePair, valueResolverExpr, Context, destValueExpr);

valueResolverExpr = propertyMap.ValueTransformers
.Concat(_typeMap.ValueTransformers)
.Concat(_typeMap.Profile.ValueTransformers)
.Where(vt => vt.IsMatch(propertyMap))
.Aggregate(valueResolverExpr, (current, vtConfig) => ToType(ReplaceParameters(vtConfig.TransformerExpression, ToType(current, vtConfig.ValueType)), propertyMap.DestinationPropertyType));

ParameterExpression propertyValue;
Expression setPropertyValue;
if (valueResolverExpr == resolvedValue)
Expand Down Expand Up @@ -465,7 +471,7 @@ private Expression BuildValueResolverFunc(PropertyMap propertyMap, Expression de
var returnType = destinationNullable && destinationPropertyType.GetTypeOfNullable() ==
nullCheckedExpression.Type
? destinationPropertyType
: nullCheckedExpression.Type;
: nullCheckedExpression.Type;
valueResolverFunc =
TryCatch(
ToType(nullCheckedExpression, returnType),
Expand Down
14 changes: 14 additions & 0 deletions src/AutoMapper/IMappingExpression.cs
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace AutoMapper
Expand Down Expand Up @@ -181,6 +182,9 @@ IMappingExpression BeforeMap<TMappingAction>()
/// <returns>Itself</returns>
IMappingExpression AfterMap<TMappingAction>()
where TMappingAction : IMappingAction<object, object>;

IList<ValueTransformerConfiguration> ValueTransformers { get; }

}

/// <summary>
Expand Down Expand Up @@ -439,5 +443,15 @@ public interface IMappingExpression<TSource, TDestination>
/// </summary>
/// <returns>Itself</returns>
IMappingExpression<TSource, TDestination> DisableCtorValidation();

IList<ValueTransformerConfiguration> ValueTransformers { get; }

/// <summary>
/// Apply a transformation function after any resolved destination member value with the given type
/// </summary>
/// <typeparam name="TValue">Value type to match and transform</typeparam>
/// <param name="transformer">Transformation expression</param>
/// <returns>Itself</returns>
IMappingExpression<TSource, TDestination> AddTransform<TValue>(Expression<Func<TValue, TValue>> transformer);
}
}
8 changes: 7 additions & 1 deletion src/AutoMapper/IMemberConfigurationExpression.cs
Expand Up @@ -10,7 +10,7 @@ namespace AutoMapper
/// <typeparam name="TSource">Source type for this member</typeparam>
/// <typeparam name="TMember">Type for this member</typeparam>
/// <typeparam name="TDestination">Destination type for this map</typeparam>
public interface IMemberConfigurationExpression<TSource, out TDestination, TMember>
public interface IMemberConfigurationExpression<TSource, TDestination, TMember>
{
/// <summary>
/// Do not precompute the execution plan for this member, just map it at runtime.
Expand Down Expand Up @@ -199,6 +199,12 @@ void ResolveUsing<TValueResolver>()
/// The destination member being configured.
/// </summary>
MemberInfo DestinationMember { get; }

/// <summary>
/// Apply a transformation function after any resolved destination member value with the given type
/// </summary>
/// <param name="transformer">Transformation expression</param>
void AddTransform(Expression<Func<TMember, TMember>> transformer);
}

/// <summary>
Expand Down
7 changes: 7 additions & 0 deletions src/AutoMapper/IProfileExpression.cs
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper.Configuration.Conventions;
using AutoMapper.Mappers;
Expand Down Expand Up @@ -152,5 +154,10 @@ public interface IProfileExpression
/// </summary>
/// <param name="type">Static type that contains extension methods</param>
void IncludeSourceExtensionMethods(Type type);

/// <summary>
/// Value transformers. Modify the list directly or use <see cref="ValueTransformerConfigurationExtensions.Add{TValue}"/>
/// </summary>
IList<ValueTransformerConfiguration> ValueTransformers { get; }
}
}
9 changes: 8 additions & 1 deletion src/AutoMapper/Profile.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using AutoMapper.Configuration;
Expand All @@ -25,8 +26,8 @@ public abstract class Profile : IProfileExpression, IProfileConfiguration
private readonly List<ITypeMapConfiguration> _openTypeMapConfigs = new List<ITypeMapConfiguration>();
private readonly List<MethodInfo> _sourceExtensionMethods = new List<MethodInfo>();
private readonly IList<ConditionalObjectMapper> _typeConfigurations = new List<ConditionalObjectMapper>();

private readonly List<ITypeMapConfiguration> _typeMapConfigs = new List<ITypeMapConfiguration>();
private readonly List<ValueTransformerConfiguration> _valueTransformerConfigs = new List<ValueTransformerConfiguration>();

protected Profile(string profileName)
: this() => ProfileName = profileName;
Expand Down Expand Up @@ -63,6 +64,7 @@ protected Profile(string profileName, Action<IProfileExpression> configurationAc
IEnumerable<IConditionalObjectMapper> IProfileConfiguration.TypeConfigurations => _typeConfigurations;
IEnumerable<ITypeMapConfiguration> IProfileConfiguration.TypeMapConfigs => _typeMapConfigs;
IEnumerable<ITypeMapConfiguration> IProfileConfiguration.OpenTypeMapConfigs => _openTypeMapConfigs;
IEnumerable<ValueTransformerConfiguration> IProfileConfiguration.ValueTransformers => _valueTransformerConfigs;

public virtual string ProfileName { get; }

Expand All @@ -75,6 +77,7 @@ protected Profile(string profileName, Action<IProfileExpression> configurationAc
public INamingConvention SourceMemberNamingConvention { get; set; }
public INamingConvention DestinationMemberNamingConvention { get; set; }

public IList<ValueTransformerConfiguration> ValueTransformers => _valueTransformerConfigs;

public void DisableConstructorMapping()
{
Expand Down Expand Up @@ -182,6 +185,10 @@ public void IncludeSourceExtensionMethods(Type type)
m.GetParameters().Length == 1));
}

public void ApplyTransform<TValue>(Expression<Func<TValue, TValue>> transformer)
{
}

private IMappingExpression<TSource, TDestination> CreateMappingExpression<TSource, TDestination>(
MemberList memberList)
{
Expand Down
2 changes: 2 additions & 0 deletions src/AutoMapper/ProfileMap.cs
Expand Up @@ -47,6 +47,7 @@ public ProfileMap(IProfileConfiguration profile, IConfiguration configuration)
: Enumerable.Empty<IConditionalObjectMapper>())
.ToArray();

ValueTransformers = profile.ValueTransformers.Concat(configuration?.ValueTransformers ?? Enumerable.Empty<ValueTransformerConfiguration>()).ToArray();

MemberConfigurations = profile.MemberConfigurations.ToArray();

Expand Down Expand Up @@ -95,6 +96,7 @@ public ProfileMap(IProfileConfiguration profile, IConfiguration configuration)
public IEnumerable<IConditionalObjectMapper> TypeConfigurations { get; }
public IEnumerable<string> Prefixes { get; }
public IEnumerable<string> Postfixes { get; }
public IEnumerable<ValueTransformerConfiguration> ValueTransformers { get; }

public TypeDetails CreateTypeDetails(Type type) => _typeDetails.GetOrAdd(type);

Expand Down
7 changes: 7 additions & 0 deletions src/AutoMapper/PropertyMap.cs
Expand Up @@ -14,6 +14,7 @@ namespace AutoMapper
public class PropertyMap
{
private readonly List<MemberInfo> _memberChain = new List<MemberInfo>();
private readonly List<ValueTransformerConfiguration> _valueTransformerConfigs = new List<ValueTransformerConfiguration>();

public PropertyMap(PathMap pathMap)
{
Expand Down Expand Up @@ -54,6 +55,7 @@ public PropertyMap(PropertyMap inheritedMappedProperty, TypeMap typeMap)
public bool ExplicitExpansion { get; set; }
public object NullSubstitute { get; set; }
public ValueResolverConfiguration ValueResolverConfig { get; set; }
public IEnumerable<ValueTransformerConfiguration> ValueTransformers => _valueTransformerConfigs;

public MemberInfo SourceMember
{
Expand Down Expand Up @@ -128,6 +130,11 @@ public void MapFrom(LambdaExpression sourceMember)
Ignored = false;
}

public void AddValueTransformation(ValueTransformerConfiguration valueTransformerConfiguration)
{
_valueTransformerConfigs.Add(valueTransformerConfiguration);
}

private class MemberFinderVisitor : ExpressionVisitor
{
public MemberExpression Member { get; private set; }
Expand Down
9 changes: 8 additions & 1 deletion src/AutoMapper/TypeMap.cs
Expand Up @@ -29,6 +29,7 @@ public class TypeMap
private PropertyMap[] _orderedPropertyMaps;
private bool _sealed;
private readonly IList<TypeMap> _inheritedTypeMaps = new List<TypeMap>();
private readonly List<ValueTransformerConfiguration> _valueTransformerConfigs = new List<ValueTransformerConfiguration>();

public TypeMap(TypeDetails sourceType, TypeDetails destinationType, MemberList memberList, ProfileMap profile)
{
Expand Down Expand Up @@ -82,7 +83,8 @@ public PathMap FindOrCreatePathMapFor(LambdaExpression destinationExpression, Me
public IEnumerable<TypePair> IncludedBaseTypes => _includedBaseTypes;

public IEnumerable<LambdaExpression> BeforeMapActions => _beforeMapActions;
public IEnumerable<LambdaExpression> AfterMapActions => _afterMapActions;
public IEnumerable<LambdaExpression> AfterMapActions => _afterMapActions;
public IEnumerable<ValueTransformerConfiguration> ValueTransformers => _valueTransformerConfigs;

public bool PreserveReferences { get; set; }
public LambdaExpression Condition { get; set; }
Expand Down Expand Up @@ -260,6 +262,11 @@ public void AddAfterMapAction(LambdaExpression afterMap)
}
}

public void AddValueTransformation(ValueTransformerConfiguration valueTransformerConfiguration)
{
_valueTransformerConfigs.Add(valueTransformerConfiguration);
}

public void Seal(IConfigurationProvider configurationProvider, Stack<TypeMap> typeMapsPath = null)
{
if(_sealed)
Expand Down
40 changes: 40 additions & 0 deletions src/AutoMapper/ValueTransformerConfiguration.cs
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace AutoMapper
{
public struct ValueTransformerConfiguration
{
public ValueTransformerConfiguration(Type valueType, LambdaExpression transformerExpression)
{
ValueType = valueType;
TransformerExpression = transformerExpression;
}

public Type ValueType { get; }
public LambdaExpression TransformerExpression { get; }

public bool IsMatch(PropertyMap propertyMap)
{
return propertyMap.DestinationPropertyType.IsAssignableFrom(ValueType);
}
}

public static class ValueTransformerConfigurationExtensions
{
/// <summary>
/// Apply a transformation function after any resolved destination member value with the given type
/// </summary>
/// <typeparam name="TValue">Value type to match and transform</typeparam>
/// <param name="valueTransformers">Value transformer list</param>
/// <param name="transformer">Transformation expression</param>
public static void Add<TValue>(this IList<ValueTransformerConfiguration> valueTransformers,
Expression<Func<TValue, TValue>> transformer)
{
var config = new ValueTransformerConfiguration(typeof(TValue), transformer);

valueTransformers.Add(config);
}
}
}
2 changes: 1 addition & 1 deletion src/UnitTests/Bug/MultiThreadingIssues.cs
Expand Up @@ -75,7 +75,7 @@ static void doMapping()
{
var source = createSource();

Console.WriteLine(@"Mapping {0} on thread {1}", source.GetType(), Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(@"Mapping {0} on thread {1}", source.GetType(), Thread.CurrentThread.ManagedThreadId);

var config = new MapperConfiguration(cfg => cfg.CreateMap(source.GetType(), typeof(DestType)));

Expand Down
12 changes: 1 addition & 11 deletions src/UnitTests/ConfigurationRules.cs
@@ -1,5 +1,4 @@
using System;
using Xunit;
using Xunit;

namespace AutoMapper.UnitTests
{
Expand Down Expand Up @@ -59,15 +58,6 @@ public void Should_throw_for_multiple_create_map_calls_in_different_profiles()
});

typeof(DuplicateTypeMapConfigurationException).ShouldBeThrownBy(() => config.AssertConfigurationIsValid());

try
{
config.AssertConfigurationIsValid();
}
catch (DuplicateTypeMapConfigurationException e)
{
Console.WriteLine(e.ToString());
}
}

[Fact]
Expand Down

0 comments on commit 01a4010

Please sign in to comment.