From f5851aaf13f633a99c7f6aa4c66e0a5c8822dee1 Mon Sep 17 00:00:00 2001 From: Tyler Carlson Date: Mon, 14 Aug 2017 23:43:09 -0500 Subject: [PATCH] Show extensions of ValueTransformerConfiguration --- .../Configuration/IProfileConfiguration.cs | 2 +- .../Configuration/MappingExpression.cs | 14 +--- .../MemberConfigurationExpression.cs | 58 +++++++--------- .../Execution/TypeMapPlanBuilder.cs | 31 ++++----- src/AutoMapper/IMappingExpression.cs | 17 ++--- .../IMemberConfigurationExpression.cs | 15 ++--- src/AutoMapper/IObjectMapper.cs | 4 +- src/AutoMapper/IProfileExpression.cs | 9 +-- .../IValueTransformConfiguration.cs | 10 +++ src/AutoMapper/Internal/ExpressionFactory.cs | 2 +- src/AutoMapper/MapperConfiguration.cs | 18 +++-- .../Mappers/EnumToUnderlyingTypeMapper.cs | 7 +- src/AutoMapper/Mappers/FlagsEnumMapper.cs | 5 +- src/AutoMapper/Mappers/FromDynamicMapper.cs | 3 +- .../CollectionMapperExpressionFactory.cs | 6 +- src/AutoMapper/Mappers/StringToEnumMapper.cs | 7 +- src/AutoMapper/Mappers/ToDynamicMapper.cs | 3 +- .../Mappers/UnderlyingTypeToEnumMapper.cs | 5 +- src/AutoMapper/Profile.cs | 12 +--- src/AutoMapper/ProfileMap.cs | 4 +- src/AutoMapper/PropertyMap.cs | 6 +- .../QueryableExtensions/ExpressionBuilder.cs | 2 +- src/AutoMapper/TypeMap.cs | 6 +- .../ValueTransformerConfiguration.cs | 67 +++++++++++++++++-- src/UnitTests/ValueTransformers.cs | 4 +- 25 files changed, 168 insertions(+), 149 deletions(-) create mode 100644 src/AutoMapper/IValueTransformConfiguration.cs diff --git a/src/AutoMapper/Configuration/IProfileConfiguration.cs b/src/AutoMapper/Configuration/IProfileConfiguration.cs index 9457d656db..4e49d9b06d 100644 --- a/src/AutoMapper/Configuration/IProfileConfiguration.cs +++ b/src/AutoMapper/Configuration/IProfileConfiguration.cs @@ -44,6 +44,6 @@ public interface IProfileConfiguration INamingConvention DestinationMemberNamingConvention { get; } IEnumerable TypeMapConfigs { get; } IEnumerable OpenTypeMapConfigs { get; } - IEnumerable ValueTransformers { get; } + IEnumerable ValueTransformers { get; } } } \ No newline at end of file diff --git a/src/AutoMapper/Configuration/MappingExpression.cs b/src/AutoMapper/Configuration/MappingExpression.cs index b23a5aba45..741bbbb91b 100644 --- a/src/AutoMapper/Configuration/MappingExpression.cs +++ b/src/AutoMapper/Configuration/MappingExpression.cs @@ -156,7 +156,7 @@ public MappingExpression(MemberList memberList, Type sourceType, Type destinatio public Type DestinationType => Types.DestinationType; public bool IsOpenGeneric { get; } public ITypeMapConfiguration ReverseTypeMap => _reverseMap; - protected List> TypeMapActions { get; } = new List>(); + public List> TypeMapActions { get; } = new List>(); public IMappingExpression PreserveReferences() { @@ -530,17 +530,7 @@ public IMappingExpression DisableCtorValidation() return this; } - public IMappingExpression ApplyTransform(Expression> transformer) - { - TypeMapActions.Add(tm => - { - var config = new ValueTransformerConfiguration(typeof(TValue), transformer); - - tm.AddValueTransformation(config); - }); - - return this; - } + private IPropertyMapConfiguration GetDestinationMemberConfiguration(MemberInfo destinationMember) => _memberConfigurations.FirstOrDefault(m => m.DestinationMember == destinationMember); diff --git a/src/AutoMapper/Configuration/MemberConfigurationExpression.cs b/src/AutoMapper/Configuration/MemberConfigurationExpression.cs index 314e66fc7d..3d9229c917 100644 --- a/src/AutoMapper/Configuration/MemberConfigurationExpression.cs +++ b/src/AutoMapper/Configuration/MemberConfigurationExpression.cs @@ -13,7 +13,7 @@ public class MemberConfigurationExpression : IMe private readonly MemberInfo _destinationMember; private LambdaExpression _sourceMember; private readonly Type _sourceType; - protected List> PropertyMapActions { get; } = new List>(); + public List> PropertyMapActions { get; } = new List>(); public MemberConfigurationExpression(MemberInfo destinationMember, Type sourceType) { @@ -23,9 +23,9 @@ public MemberConfigurationExpression(MemberInfo destinationMember, Type sourceTy public MemberInfo DestinationMember => _destinationMember; - public void MapAtRuntime() - { - PropertyMapActions.Add(pm => pm.Inline = false); + public void MapAtRuntime() + { + PropertyMapActions.Add(pm => pm.Inline = false); } public void NullSubstitute(object nullSubstitute) @@ -125,7 +125,7 @@ public void MapFrom(Expression> sour MapFromUntyped(sourceMember); } - internal void MapFromUntyped(LambdaExpression sourceExpression) + internal void MapFromUntyped(LambdaExpression sourceExpression) { _sourceMember = sourceExpression; PropertyMapActions.Add(pm => pm.MapFrom(sourceExpression)); @@ -233,31 +233,21 @@ public void PreCondition(Func condition) }); } - public void ApplyTransform(Expression> transformer) - { - PropertyMapActions.Add(pm => - { - var config = new ValueTransformerConfiguration(typeof(TValue), transformer); - - pm.AddValueTransformation(config); - }); - } - public void ExplicitExpansion() { PropertyMapActions.Add(pm => pm.ExplicitExpansion = true); } - public void Ignore() => Ignore(ignorePaths: true); + public void Ignore() => Ignore(ignorePaths: true); internal void Ignore(bool ignorePaths) => - PropertyMapActions.Add(pm => - { + PropertyMapActions.Add(pm => + { pm.Ignored = true; - if(ignorePaths) - { - pm.TypeMap.IgnorePaths(DestinationMember); - } + if(ignorePaths) + { + pm.TypeMap.IgnorePaths(DestinationMember); + } }); public void AllowNull() @@ -284,25 +274,25 @@ public void Configure(TypeMap typeMap) destMember = typeMap.DestinationTypeDetails.PublicReadAccessors.Single(m => m.Name == destMember.Name); } - var propertyMap = typeMap.FindOrCreatePropertyMapFor(destMember); - + var propertyMap = typeMap.FindOrCreatePropertyMapFor(destMember); + Apply(propertyMap); - } - - private void Apply(PropertyMap propertyMap) - { + } + + private void Apply(PropertyMap propertyMap) + { foreach(var action in PropertyMapActions) { action(propertyMap); - } - } - - public IPropertyMapConfiguration Reverse() + } + } + + public IPropertyMapConfiguration Reverse() { var newSource = Parameter(DestinationMember.DeclaringType, "source"); var newSourceProperty = MakeMemberAccess(newSource, _destinationMember); - var newSourceExpression = Lambda(newSourceProperty, newSource); + var newSourceExpression = Lambda(newSourceProperty, newSource); return PathConfigurationExpression.Create(_sourceMember, newSourceExpression); - } + } } } \ No newline at end of file diff --git a/src/AutoMapper/Execution/TypeMapPlanBuilder.cs b/src/AutoMapper/Execution/TypeMapPlanBuilder.cs index a9b1bc8efb..d954ab76d6 100644 --- a/src/AutoMapper/Execution/TypeMapPlanBuilder.cs +++ b/src/AutoMapper/Execution/TypeMapPlanBuilder.cs @@ -1,3 +1,5 @@ +using AutoMapper.Internal; + namespace AutoMapper.Execution { using System; @@ -136,7 +138,7 @@ private LambdaExpression TypeConverterMapper() typeof(ITypeConverter<,>).MakeGenericType(_typeMap.SourceType, _typeMap.DestinationTypeToUse); return Lambda( Call( - ToType(CreateInstance(type), converterInterfaceType), + CreateInstance(type).ToType(converterInterfaceType), converterInterfaceType.GetDeclaredMethod("Convert"), Source, _initialDestination, Context ), @@ -145,7 +147,7 @@ private LambdaExpression TypeConverterMapper() private Expression CreateDestinationFunc(out bool constructorMapping) { - var newDestFunc = ToType(CreateNewDestinationFunc(out constructorMapping), _typeMap.DestinationTypeToUse); + var newDestFunc = CreateNewDestinationFunc(out constructorMapping).ToType(_typeMap.DestinationTypeToUse); var getDest = _typeMap.DestinationTypeToUse.IsValueType() ? newDestFunc @@ -250,7 +252,7 @@ private Expression CreateMapperFunc(Expression assignmentFunc) var getDestination = Context.Type.GetDeclaredMethod("GetDestination"); var assignCache = Assign(cache, - ToType(Call(Context, getDestination, Source, Constant(_destination.Type)), _destination.Type)); + Call(Context, getDestination, Source, Constant(_destination.Type)).ToType(_destination.Type)); var condition = Condition( AndAlso(NotEqual(Source, Constant(null)), NotEqual(assignCache, Constant(null))), cache, @@ -408,7 +410,7 @@ private Expression CreatePropertyMapFunc(PropertyMap propertyMap, Expression des if (propertyMap.DestinationProperty is FieldInfo) { mapperExpr = propertyMap.SourceType != propertyMap.DestinationPropertyType - ? Assign(destMember, ToType(propertyValue, propertyMap.DestinationPropertyType)) + ? Assign(destMember, propertyValue.ToType(propertyMap.DestinationPropertyType)) : Assign(getter, propertyValue); } else @@ -417,7 +419,7 @@ private Expression CreatePropertyMapFunc(PropertyMap propertyMap, Expression des if (setter == null) mapperExpr = propertyValue; else - mapperExpr = Assign(destMember, ToType(propertyValue, propertyMap.DestinationPropertyType)); + mapperExpr = Assign(destMember, propertyValue.ToType(propertyMap.DestinationPropertyType)); } if (propertyMap.Condition != null) @@ -425,8 +427,8 @@ private Expression CreatePropertyMapFunc(PropertyMap propertyMap, Expression des propertyMap.Condition.ConvertReplaceParameters( Source, _destination, - ToType(propertyValue, propertyMap.Condition.Parameters[2].Type), - ToType(getter, propertyMap.Condition.Parameters[2].Type), + propertyValue.ToType(propertyMap.Condition.Parameters[2].Type), + getter.ToType(propertyMap.Condition.Parameters[2].Type), Context ), mapperExpr @@ -452,8 +454,7 @@ private Expression BuildValueResolverFunc(PropertyMap propertyMap, Expression de if (valueResolverConfig != null) { - valueResolverFunc = ToType(BuildResolveCall(destValueExpr, valueResolverConfig), - destinationPropertyType); + valueResolverFunc = BuildResolveCall(destValueExpr, valueResolverConfig).ToType(destinationPropertyType); } else if (propertyMap.CustomResolver != null) { @@ -471,7 +472,7 @@ private Expression BuildValueResolverFunc(PropertyMap propertyMap, Expression de : nullCheckedExpression.Type; valueResolverFunc = TryCatch( - ToType(nullCheckedExpression, returnType), + nullCheckedExpression.ToType(returnType), Catch(typeof(NullReferenceException), Default(returnType)), Catch(typeof(ArgumentNullException), Default(returnType)) ); @@ -510,7 +511,7 @@ private Expression BuildValueResolverFunc(PropertyMap propertyMap, Expression de if (propertyMap.NullSubstitute != null) { var nullSubstitute = Constant(propertyMap.NullSubstitute); - valueResolverFunc = Coalesce(valueResolverFunc, ToType(nullSubstitute, valueResolverFunc.Type)); + valueResolverFunc = Coalesce(valueResolverFunc, nullSubstitute.ToType(valueResolverFunc.Type)); } else if (!typeMap.Profile.AllowNullDestinationValues) { @@ -518,7 +519,7 @@ private Expression BuildValueResolverFunc(PropertyMap propertyMap, Expression de if (!toCreate.IsAbstract() && toCreate.IsClass()) valueResolverFunc = Coalesce( valueResolverFunc, - ToType(DelegateFactory.GenerateNonNullConstructorExpression(toCreate), propertyMap.SourceType) + DelegateFactory.GenerateNonNullConstructorExpression(toCreate).ToType(propertyMap.SourceType) ); } @@ -526,7 +527,7 @@ private Expression BuildValueResolverFunc(PropertyMap propertyMap, Expression de .Concat(_typeMap.ValueTransformers) .Concat(_typeMap.Profile.ValueTransformers) .Where(vt => vt.IsMatch(propertyMap)) - .Aggregate(valueResolverFunc, (current, vtConfig) => ToType(ReplaceParameters(vtConfig.TransformerExpression, ToType(current, vtConfig.ValueType)), propertyMap.DestinationPropertyType)); + .Aggregate(valueResolverFunc, (current, vtConfig) => vtConfig.Visit(current, propertyMap)); return valueResolverFunc; } @@ -549,9 +550,9 @@ private Expression BuildResolveCall(Expression destValueExpr, ValueResolverConfi var iResolverType = valueResolverConfig.InterfaceType; var parameters = new[] {Source, _destination, sourceMember, destValueExpr}.Where(p => p != null) - .Zip(iResolverType.GetGenericArguments(), ToType) + .Zip(iResolverType.GetGenericArguments(), ExpressionFactory.ToType) .Concat(new[] {Context}); - return Call(ToType(resolverInstance, iResolverType), iResolverType.GetDeclaredMethod("Resolve"), + return Call(resolverInstance.ToType(iResolverType), iResolverType.GetDeclaredMethod("Resolve"), parameters); } } diff --git a/src/AutoMapper/IMappingExpression.cs b/src/AutoMapper/IMappingExpression.cs index 6cc44d5f3a..5bfec00bc4 100644 --- a/src/AutoMapper/IMappingExpression.cs +++ b/src/AutoMapper/IMappingExpression.cs @@ -1,12 +1,17 @@ using System; +using System.Collections.Generic; using System.Linq.Expressions; namespace AutoMapper { + public interface IMappingExpressionBase + { + List> TypeMapActions { get; } + } /// /// Mapping configuration options for non-generic maps /// - public interface IMappingExpression + public interface IMappingExpression : IMappingExpressionBase { /// /// Preserve object identity. Useful for circular references. @@ -188,7 +193,7 @@ IMappingExpression AfterMap() /// /// Source type /// Destination type - public interface IMappingExpression + public interface IMappingExpression : IMappingExpressionBase { /// /// Customize configuration for a path inside the destination object. @@ -439,13 +444,5 @@ IMappingExpression ForSourceMember(string sourceMemberNam /// /// Itself IMappingExpression DisableCtorValidation(); - - /// - /// Apply a transformation function after any resolved destination member value with the given type - /// - /// Value type to match and transform - /// Transformation expression - /// Itself - IMappingExpression ApplyTransform(Expression> transformer); } } \ No newline at end of file diff --git a/src/AutoMapper/IMemberConfigurationExpression.cs b/src/AutoMapper/IMemberConfigurationExpression.cs index 54b1a2aefb..d4eaf132ec 100644 --- a/src/AutoMapper/IMemberConfigurationExpression.cs +++ b/src/AutoMapper/IMemberConfigurationExpression.cs @@ -1,16 +1,22 @@ using System; +using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; namespace AutoMapper { + public interface IMemberConfigurationExpressionBase + { + List> PropertyMapActions { get; } + } + /// /// Member configuration options /// /// Source type for this member /// Type for this member /// Destination type for this map - public interface IMemberConfigurationExpression + public interface IMemberConfigurationExpression : IMemberConfigurationExpressionBase { /// /// Do not precompute the execution plan for this member, just map it at runtime. @@ -199,13 +205,6 @@ void ResolveUsing(string sourceMemberName) /// The destination member being configured. /// MemberInfo DestinationMember { get; } - - /// - /// Apply a transformation function after any resolved destination member value with the given type - /// - /// Value type to match and transform - /// Transformation expression - void ApplyTransform(Expression> transformer); } /// diff --git a/src/AutoMapper/IObjectMapper.cs b/src/AutoMapper/IObjectMapper.cs index 92e13ad4ed..e7de5e81e8 100644 --- a/src/AutoMapper/IObjectMapper.cs +++ b/src/AutoMapper/IObjectMapper.cs @@ -63,8 +63,8 @@ public Expression MapExpression(IConfigurationProvider configurationProvider, Pr Call( Constant(this), MapMethod, - ToType(sourceExpression, typeof(TSource)), - ToType(destExpression, typeof(TDestination)), + sourceExpression.ToType(typeof(TSource)), + destExpression.ToType(typeof(TDestination)), contextExpression); } } \ No newline at end of file diff --git a/src/AutoMapper/IProfileExpression.cs b/src/AutoMapper/IProfileExpression.cs index 2ea40a397a..0b8a2d4278 100644 --- a/src/AutoMapper/IProfileExpression.cs +++ b/src/AutoMapper/IProfileExpression.cs @@ -1,5 +1,5 @@ using System; -using System.Linq.Expressions; +using System.Collections.Generic; using System.Reflection; using AutoMapper.Configuration.Conventions; using AutoMapper.Mappers; @@ -154,11 +154,6 @@ public interface IProfileExpression /// Static type that contains extension methods void IncludeSourceExtensionMethods(Type type); - /// - /// Apply a transformation function after any resolved destination member value with the given type - /// - /// Value type to match and transform - /// Transformation expression - void ApplyTransform(Expression> transformer); + IList ValueTransformers { get; } } } \ No newline at end of file diff --git a/src/AutoMapper/IValueTransformConfiguration.cs b/src/AutoMapper/IValueTransformConfiguration.cs new file mode 100644 index 0000000000..c3c7f3e374 --- /dev/null +++ b/src/AutoMapper/IValueTransformConfiguration.cs @@ -0,0 +1,10 @@ +using System.Linq.Expressions; + +namespace AutoMapper +{ + public interface IValueTransformConfiguration + { + bool IsMatch(PropertyMap propertyMap); + Expression Visit(Expression current, PropertyMap propertyMap); + } +} \ No newline at end of file diff --git a/src/AutoMapper/Internal/ExpressionFactory.cs b/src/AutoMapper/Internal/ExpressionFactory.cs index d97f93216e..fc28912749 100644 --- a/src/AutoMapper/Internal/ExpressionFactory.cs +++ b/src/AutoMapper/Internal/ExpressionFactory.cs @@ -88,7 +88,7 @@ public static Expression ToObject(Expression expression) => ? expression : Expression.Convert(expression, typeof(object)); - public static Expression ToType(Expression expression, Type type) => + public static Expression ToType(this Expression expression, Type type) => expression.Type == type ? expression : Expression.Convert(expression, type); diff --git a/src/AutoMapper/MapperConfiguration.cs b/src/AutoMapper/MapperConfiguration.cs index b218ebfc90..55d59db2eb 100644 --- a/src/AutoMapper/MapperConfiguration.cs +++ b/src/AutoMapper/MapperConfiguration.cs @@ -127,11 +127,11 @@ private static LambdaExpression GenerateTypeMapExpression(MapRequest mapRequest, var requestedDestinationParameter = Parameter(requestedDestinationType, "typeMapDestination"); var contextParameter = Parameter(typeof(ResolutionContext), "context"); - mapExpression = Lambda(ToType(Invoke(typeMap.MapExpression, - ToType(requestedSourceParameter, typeMapSourceParameter.Type), - ToType(requestedDestinationParameter, typeMapDestinationParameter.Type), + mapExpression = Lambda(Invoke(typeMap.MapExpression, + requestedSourceParameter.ToType(typeMapSourceParameter.Type), + requestedDestinationParameter.ToType(typeMapDestinationParameter.Type), contextParameter - ), mapRequest.RuntimeTypes.DestinationType), + ).ToType(mapRequest.RuntimeTypes.DestinationType), requestedSourceParameter, requestedDestinationParameter, contextParameter); } @@ -154,12 +154,12 @@ private LambdaExpression GenerateObjectMapperExpression(MapRequest mapRequest, I else { var map = mapperToUse.MapExpression(mapperConfiguration, Configuration, null, - ToType(source, mapRequest.RuntimeTypes.SourceType), - ToType(destination, mapRequest.RuntimeTypes.DestinationType), + source.ToType(mapRequest.RuntimeTypes.SourceType), + destination.ToType(mapRequest.RuntimeTypes.DestinationType), context); var exception = Parameter(typeof(Exception), "ex"); fullExpression = - TryCatch(ToType(map, destinationType), + TryCatch(map.ToType(destinationType), MakeCatchBlock(typeof(Exception), exception, Block( Throw(New(ExceptionConstructor, Constant("Error mapping types."), exception, Constant(mapRequest.RequestedTypes))), Default(destination.Type)), null)); @@ -389,9 +389,7 @@ private static Expression Wrap(MapRequest mapRequest, LambdaE var destination = requestedDestinationType.IsValueType() ? Coalesce(destinationParameter, New(requestedDestinationType)) : (Expression)destinationParameter; // Invoking a delegate here return Lambda( - ToType( - Invoke(typedExpression, ToType(sourceParameter, requestedSourceType), ToType(destination, requestedDestinationType), contextParameter) - , typeof(object)), + Invoke(typedExpression, sourceParameter.ToType(requestedSourceType), destination.ToType(requestedDestinationType), contextParameter).ToType(typeof(object)), sourceParameter, destinationParameter, contextParameter); } } diff --git a/src/AutoMapper/Mappers/EnumToUnderlyingTypeMapper.cs b/src/AutoMapper/Mappers/EnumToUnderlyingTypeMapper.cs index 3873456b68..0144c0979e 100644 --- a/src/AutoMapper/Mappers/EnumToUnderlyingTypeMapper.cs +++ b/src/AutoMapper/Mappers/EnumToUnderlyingTypeMapper.cs @@ -24,11 +24,6 @@ public bool IsMatch(TypePair context) public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression) => - ToType( - Call(ChangeTypeMethod, ToObject(sourceExpression), - Constant(destExpression.Type)), - destExpression.Type - ); + Call(ChangeTypeMethod, ToObject(sourceExpression),Constant(destExpression.Type)).ToType(destExpression.Type); } - } \ No newline at end of file diff --git a/src/AutoMapper/Mappers/FlagsEnumMapper.cs b/src/AutoMapper/Mappers/FlagsEnumMapper.cs index 741e9ced87..7a928f36e9 100644 --- a/src/AutoMapper/Mappers/FlagsEnumMapper.cs +++ b/src/AutoMapper/Mappers/FlagsEnumMapper.cs @@ -28,13 +28,10 @@ public bool IsMatch(TypePair context) public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression) => - ToType( Call(EnumParseMethod, Constant(destExpression.Type), Call(sourceExpression, sourceExpression.Type.GetDeclaredMethod("ToString")), Constant(true) - ), - destExpression.Type - ); + ).ToType(destExpression.Type); } } \ No newline at end of file diff --git a/src/AutoMapper/Mappers/FromDynamicMapper.cs b/src/AutoMapper/Mappers/FromDynamicMapper.cs index 8be145de2e..dfaa333242 100644 --- a/src/AutoMapper/Mappers/FromDynamicMapper.cs +++ b/src/AutoMapper/Mappers/FromDynamicMapper.cs @@ -53,9 +53,8 @@ public Expression MapExpression(IConfigurationProvider configurationProvider, Pr Call(null, MapMethodInfo.MakeGenericMethod(sourceExpression.Type, destExpression.Type), sourceExpression, - ToType( Coalesce(ToObject(destExpression), - DelegateFactory.GenerateConstructorExpression(destExpression.Type)), destExpression.Type), + DelegateFactory.GenerateConstructorExpression(destExpression.Type)).ToType(destExpression.Type), contextExpression, Constant(profileMap)); } diff --git a/src/AutoMapper/Mappers/Internal/CollectionMapperExpressionFactory.cs b/src/AutoMapper/Mappers/Internal/CollectionMapperExpressionFactory.cs index 6350f09215..eb90d3f1d1 100644 --- a/src/AutoMapper/Mappers/Internal/CollectionMapperExpressionFactory.cs +++ b/src/AutoMapper/Mappers/Internal/CollectionMapperExpressionFactory.cs @@ -45,7 +45,7 @@ public static Expression MapCollectionExpression(IConfigurationProvider configur IfThenElse(condition ?? Constant(false), Block(Assign(newExpression, passedDestination), Call(newExpression, clearMethod)), Assign(newExpression, passedDestination.Type.NewExpr(ifInterfaceType))), - ToType(mapExpr, passedDestination.Type) + mapExpr.ToType(passedDestination.Type) ); if (propertyMap != null) return checkNull; @@ -65,7 +65,7 @@ private static Expression NewExpr(this Type baseType, Type ifInterfaceType) ifInterfaceType.MakeGenericType(ElementTypeHelper.GetElementTypes(baseType, ElementTypeFlags.BreakKeyValuePair))) : DelegateFactory.GenerateConstructorExpression(baseType); - return ToType(newExpr, baseType); + return newExpr.ToType(baseType); } public static Expression MapItemExpr(IConfigurationProvider configurationProvider, ProfileMap profileMap, PropertyMap propertyMap, Type sourceType, Type destType, Expression contextParam, out ParameterExpression itemParam) @@ -78,7 +78,7 @@ public static Expression MapItemExpr(IConfigurationProvider configurationProvide var itemExpr = MapExpression(configurationProvider, profileMap, typePair, itemParam, contextParam, propertyMap); - return ToType(itemExpr, destElementType); + return itemExpr.ToType(destElementType); } public static Expression MapKeyPairValueExpr(IConfigurationProvider configurationProvider, ProfileMap profileMap, PropertyMap propertyMap, Type sourceType, Type destType, Expression contextParam, out ParameterExpression itemParam) diff --git a/src/AutoMapper/Mappers/StringToEnumMapper.cs b/src/AutoMapper/Mappers/StringToEnumMapper.cs index 1c5198bc17..6c26784a20 100644 --- a/src/AutoMapper/Mappers/StringToEnumMapper.cs +++ b/src/AutoMapper/Mappers/StringToEnumMapper.cs @@ -32,15 +32,14 @@ public Expression MapExpression(IConfigurationProvider configurationProvider, Pr if (attribute?.Value != null) { var switchCase = Expression.SwitchCase( - ToType(Expression.Constant(Enum.ToObject(destinationEnumType, memberInfo.GetMemberValue(null))), - destinationType), Expression.Constant(attribute.Value)); + Expression.Constant(Enum.ToObject(destinationEnumType, memberInfo.GetMemberValue(null))).ToType(destinationType), Expression.Constant(attribute.Value)); switchCases.Add(switchCase); } } var equalsMethodInfo = Method(() => StringCompareOrdinalIgnoreCase(null, null)); var switchTable = switchCases.Count > 0 - ? Expression.Switch(sourceExpression, ToType(enumParse, destinationType), equalsMethodInfo, switchCases) - : ToType(enumParse, destinationType); + ? Expression.Switch(sourceExpression, enumParse.ToType(destinationType), equalsMethodInfo, switchCases) + : enumParse.ToType(destinationType); var isNullOrEmpty = Expression.Call(typeof(string), "IsNullOrEmpty", null, sourceExpression); return Expression.Condition(isNullOrEmpty, Expression.Default(destinationType), switchTable); } diff --git a/src/AutoMapper/Mappers/ToDynamicMapper.cs b/src/AutoMapper/Mappers/ToDynamicMapper.cs index 5249924d05..1ecea7c106 100644 --- a/src/AutoMapper/Mappers/ToDynamicMapper.cs +++ b/src/AutoMapper/Mappers/ToDynamicMapper.cs @@ -55,9 +55,8 @@ public Expression MapExpression(IConfigurationProvider configurationProvider, Pr Call(null, MapMethodInfo.MakeGenericMethod(sourceExpression.Type, destExpression.Type), sourceExpression, - ToType( Coalesce(ToObject(destExpression), - DelegateFactory.GenerateConstructorExpression(destExpression.Type)), destExpression.Type), + DelegateFactory.GenerateConstructorExpression(destExpression.Type)).ToType(destExpression.Type), contextExpression, Constant(profileMap)); } diff --git a/src/AutoMapper/Mappers/UnderlyingTypeToEnumMapper.cs b/src/AutoMapper/Mappers/UnderlyingTypeToEnumMapper.cs index 344336d35f..6ef64a2912 100644 --- a/src/AutoMapper/Mappers/UnderlyingTypeToEnumMapper.cs +++ b/src/AutoMapper/Mappers/UnderlyingTypeToEnumMapper.cs @@ -23,10 +23,7 @@ public bool IsMatch(TypePair context) public Expression MapExpression(IConfigurationProvider configurationProvider, ProfileMap profileMap, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression) => - ToType( Call(EnumToObject, Constant(destExpression.Type), - ToObject(sourceExpression)), - destExpression.Type - ); + ToObject(sourceExpression)).ToType(destExpression.Type); } } \ No newline at end of file diff --git a/src/AutoMapper/Profile.cs b/src/AutoMapper/Profile.cs index 8020dbefd2..916344dd1f 100644 --- a/src/AutoMapper/Profile.cs +++ b/src/AutoMapper/Profile.cs @@ -27,7 +27,7 @@ public abstract class Profile : IProfileExpression, IProfileConfiguration private readonly List _sourceExtensionMethods = new List(); private readonly IList _typeConfigurations = new List(); private readonly List _typeMapConfigs = new List(); - private readonly List _valueTransformerConfigs = new List(); + private readonly List _valueTransformerConfigs = new List(); protected Profile(string profileName) : this() => ProfileName = profileName; @@ -64,7 +64,8 @@ IEnumerable> IProfileConfigu IEnumerable IProfileConfiguration.TypeConfigurations => _typeConfigurations; IEnumerable IProfileConfiguration.TypeMapConfigs => _typeMapConfigs; IEnumerable IProfileConfiguration.OpenTypeMapConfigs => _openTypeMapConfigs; - IEnumerable IProfileConfiguration.ValueTransformers => _valueTransformerConfigs; + IEnumerable IProfileConfiguration.ValueTransformers => _valueTransformerConfigs; + IList IProfileExpression.ValueTransformers => _valueTransformerConfigs; public virtual string ProfileName { get; } @@ -184,13 +185,6 @@ public void IncludeSourceExtensionMethods(Type type) m.GetParameters().Length == 1)); } - public void ApplyTransform(Expression> transformer) - { - var config = new ValueTransformerConfiguration(typeof(TValue), transformer); - - _valueTransformerConfigs.Add(config); - } - private IMappingExpression CreateMappingExpression( MemberList memberList) { diff --git a/src/AutoMapper/ProfileMap.cs b/src/AutoMapper/ProfileMap.cs index e36020b9c1..0f0e622f3d 100644 --- a/src/AutoMapper/ProfileMap.cs +++ b/src/AutoMapper/ProfileMap.cs @@ -47,7 +47,7 @@ public ProfileMap(IProfileConfiguration profile, IConfiguration configuration) : Enumerable.Empty()) .ToArray(); - ValueTransformers = profile.ValueTransformers.Concat(configuration?.ValueTransformers ?? Enumerable.Empty()).ToArray(); + ValueTransformers = profile.ValueTransformers.Concat(configuration?.ValueTransformers ?? Enumerable.Empty()).ToArray(); MemberConfigurations = profile.MemberConfigurations.ToArray(); @@ -96,7 +96,7 @@ public ProfileMap(IProfileConfiguration profile, IConfiguration configuration) public IEnumerable TypeConfigurations { get; } public IEnumerable Prefixes { get; } public IEnumerable Postfixes { get; } - public IEnumerable ValueTransformers { get; } + public IEnumerable ValueTransformers { get; } public TypeDetails CreateTypeDetails(Type type) => _typeDetails.GetOrAdd(type); diff --git a/src/AutoMapper/PropertyMap.cs b/src/AutoMapper/PropertyMap.cs index 33e693c26b..66dceb852f 100644 --- a/src/AutoMapper/PropertyMap.cs +++ b/src/AutoMapper/PropertyMap.cs @@ -17,7 +17,7 @@ namespace AutoMapper public class PropertyMap { private readonly List _memberChain = new List(); - private readonly List _valueTransformerConfigs = new List(); + private readonly List _valueTransformerConfigs = new List(); public PropertyMap(PathMap pathMap) { @@ -69,7 +69,7 @@ public PropertyMap(PropertyMap inheritedMappedProperty, TypeMap typeMap) public bool ExplicitExpansion { get; set; } public object NullSubstitute { get; set; } public ValueResolverConfiguration ValueResolverConfig { get; set; } - public IEnumerable ValueTransformers => _valueTransformerConfigs; + public IEnumerable ValueTransformers => _valueTransformerConfigs; public MemberInfo SourceMember { @@ -144,7 +144,7 @@ public void MapFrom(LambdaExpression sourceMember) Ignored = false; } - public void AddValueTransformation(ValueTransformerConfiguration valueTransformerConfiguration) + public void AddValueTransformation(IValueTransformConfiguration valueTransformerConfiguration) { _valueTransformerConfigs.Add(valueTransformerConfiguration); } diff --git a/src/AutoMapper/QueryableExtensions/ExpressionBuilder.cs b/src/AutoMapper/QueryableExtensions/ExpressionBuilder.cs index 760ae691b8..4b3166844f 100644 --- a/src/AutoMapper/QueryableExtensions/ExpressionBuilder.cs +++ b/src/AutoMapper/QueryableExtensions/ExpressionBuilder.cs @@ -300,7 +300,7 @@ public NullSubstitutionConversionVisitor(Expression newParameter, object nullSub private Expression NullCheck(Expression input) { var underlyingType = input.Type.GetTypeOfNullable(); - var nullSubstitute = ToType(Constant(_nullSubstitute), underlyingType); + var nullSubstitute = Constant(_nullSubstitute).ToType(underlyingType); return Condition(Property(input, "HasValue"), Property(input, "Value"), nullSubstitute, underlyingType); } } diff --git a/src/AutoMapper/TypeMap.cs b/src/AutoMapper/TypeMap.cs index 1da9a84ad4..5d139ae210 100644 --- a/src/AutoMapper/TypeMap.cs +++ b/src/AutoMapper/TypeMap.cs @@ -29,7 +29,7 @@ public class TypeMap private PropertyMap[] _orderedPropertyMaps; private bool _sealed; private readonly IList _inheritedTypeMaps = new List(); - private readonly List _valueTransformerConfigs = new List(); + private readonly List _valueTransformerConfigs = new List(); public TypeMap(TypeDetails sourceType, TypeDetails destinationType, MemberList memberList, ProfileMap profile) { @@ -81,7 +81,7 @@ public PathMap FindOrCreatePathMapFor(LambdaExpression destinationExpression, Me public IEnumerable BeforeMapActions => _beforeMapActions; public IEnumerable AfterMapActions => _afterMapActions; - public IEnumerable ValueTransformers => _valueTransformerConfigs; + public IEnumerable ValueTransformers => _valueTransformerConfigs; public bool PreserveReferences { get; set; } public LambdaExpression Condition { get; set; } @@ -261,7 +261,7 @@ public void AddAfterMapAction(LambdaExpression afterMap) } } - public void AddValueTransformation(ValueTransformerConfiguration valueTransformerConfiguration) + public void AddValueTransformation(IValueTransformConfiguration valueTransformerConfiguration) { _valueTransformerConfigs.Add(valueTransformerConfiguration); } diff --git a/src/AutoMapper/ValueTransformerConfiguration.cs b/src/AutoMapper/ValueTransformerConfiguration.cs index e3c97ee64d..9a1bc0305d 100644 --- a/src/AutoMapper/ValueTransformerConfiguration.cs +++ b/src/AutoMapper/ValueTransformerConfiguration.cs @@ -1,22 +1,81 @@ using System; using System.Linq.Expressions; +using AutoMapper.Internal; namespace AutoMapper { - public class ValueTransformerConfiguration + public class ValueTransformConfiguration : IValueTransformConfiguration { - public ValueTransformerConfiguration(Type valueType, LambdaExpression transformerExpression) + public ValueTransformConfiguration(Type valueType, LambdaExpression transformerExpression) { ValueType = valueType; TransformerExpression = transformerExpression; } - public Type ValueType { get; } - public LambdaExpression TransformerExpression { get; } + private Type ValueType { get; } + private LambdaExpression TransformerExpression { get; } public bool IsMatch(PropertyMap propertyMap) { return ValueType.IsAssignableFrom(propertyMap.DestinationPropertyType); } + + public Expression Visit(Expression expression, PropertyMap propertyMap) + { + return TransformerExpression.ReplaceParameters(expression.ToType(ValueType)).ToType(propertyMap.DestinationPropertyType); + } + } + + public static class ValueTransformerConfigurationExtensions + { + /// + /// Apply a transformation function after any resolved destination member value with the given type + /// + /// Value type to match and transform + /// + /// + /// Transformation expression + /// Itself + public static T ApplyTransform(this T mappingExpression, Expression> transformer) + where T : IMappingExpressionBase + { + mappingExpression.TypeMapActions.Add(tm => + { + var config = new ValueTransformConfiguration(typeof(TValue), transformer); + + tm.AddValueTransformation(config); + }); + + return mappingExpression; + } + + /// + /// Apply a transformation function after any resolved destination member value with the given type + /// + /// Value type to match and transform + /// + /// Transformation expression + public static void ApplyTransform(this IProfileExpression profileExpression, Expression> transformer) + { + var config = new ValueTransformConfiguration(typeof(TValue), transformer); + + profileExpression.ValueTransformers.Add(config); + } + + /// + /// Apply a transformation function after any resolved destination member value with the given type + /// + /// Value type to match and transform + /// + /// Transformation expression + public static void ApplyTransform(this IMemberConfigurationExpressionBase self, Expression> transformer) + { + self.PropertyMapActions.Add(pm => + { + var config = new ValueTransformConfiguration(typeof(TValue), transformer); + + pm.AddValueTransformation(config); + }); + } } } \ No newline at end of file diff --git a/src/UnitTests/ValueTransformers.cs b/src/UnitTests/ValueTransformers.cs index 3f4030af5b..9b6e49dd80 100644 --- a/src/UnitTests/ValueTransformers.cs +++ b/src/UnitTests/ValueTransformers.cs @@ -188,7 +188,7 @@ public class Dest cfg.CreateProfile("Other", p => { p.CreateMap() - .ApplyTransform(dest => dest + ", for real,"); + .ApplyTransform, string>(dest => dest + ", for real,"); p.ApplyTransform(dest => dest + " is straight up dope"); }); }); @@ -224,7 +224,7 @@ public class Dest cfg.CreateProfile("Other", p => { p.CreateMap() - .ApplyTransform(dest => dest + ", for real,") + .ApplyTransform, string>(dest => dest + ", for real,") .ForMember(d => d.Value, opt => opt.ApplyTransform(d => d + ", seriously")); p.ApplyTransform(dest => dest + " is straight up dope"); });