diff --git a/src/AutoMapper/Configuration/CtorParamConfigurationExpression.cs b/src/AutoMapper/Configuration/CtorParamConfigurationExpression.cs index 65d4053551..cb1231a065 100644 --- a/src/AutoMapper/Configuration/CtorParamConfigurationExpression.cs +++ b/src/AutoMapper/Configuration/CtorParamConfigurationExpression.cs @@ -43,5 +43,10 @@ public void Configure(TypeMap typeMap) action(parameter); } } + + public bool CheckCtorParamName(string paramName) + { + return _ctorParamName == paramName; + } } } \ No newline at end of file diff --git a/src/AutoMapper/Configuration/ICtorParameterConfiguration.cs b/src/AutoMapper/Configuration/ICtorParameterConfiguration.cs index 59e126b4ce..67c87019fb 100644 --- a/src/AutoMapper/Configuration/ICtorParameterConfiguration.cs +++ b/src/AutoMapper/Configuration/ICtorParameterConfiguration.cs @@ -3,5 +3,6 @@ public interface ICtorParameterConfiguration { void Configure(TypeMap typeMap); + bool CheckCtorParamName(string paramName); } } \ No newline at end of file diff --git a/src/AutoMapper/Configuration/ITypeMapConfiguration.cs b/src/AutoMapper/Configuration/ITypeMapConfiguration.cs index bb03c14a95..92f60f073b 100644 --- a/src/AutoMapper/Configuration/ITypeMapConfiguration.cs +++ b/src/AutoMapper/Configuration/ITypeMapConfiguration.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace AutoMapper.Configuration { diff --git a/src/AutoMapper/Configuration/MappingExpressionBase.cs b/src/AutoMapper/Configuration/MappingExpressionBase.cs index 150bd171ac..ab397ec59a 100644 --- a/src/AutoMapper/Configuration/MappingExpressionBase.cs +++ b/src/AutoMapper/Configuration/MappingExpressionBase.cs @@ -40,7 +40,7 @@ protected MappingExpressionBase(MemberList memberList, TypePair types) public void Configure(TypeMap typeMap) { - foreach(var destProperty in typeMap.DestinationTypeDetails.PublicWriteAccessors) + foreach (var destProperty in typeMap.DestinationTypeDetails.PublicWriteAccessors) { var attrs = destProperty.GetCustomAttributes(true); if(attrs.Any(x => x is IgnoreMapAttribute)) @@ -58,7 +58,20 @@ public void Configure(TypeMap typeMap) } } - foreach(var action in TypeMapActions) + var destTypeInfo = typeMap.DestinationTypeDetails; + if (!typeMap.DestinationType.IsAbstract()) + { + foreach (var destCtor in destTypeInfo.Constructors.OrderByDescending(ci => ci.GetParameters().Length)) + { + if (MapDestinationCtorToSource(typeMap, destCtor, typeMap.SourceTypeDetails, typeMap.Profile)) + { + break; + } + } + } + + + foreach (var action in TypeMapActions) { action(typeMap); } @@ -100,6 +113,41 @@ public void Configure(TypeMap typeMap) } } + private bool MapDestinationCtorToSource(TypeMap typeMap, ConstructorInfo destCtor, TypeDetails sourceTypeInfo, ProfileMap options) + { + var ctorParameters = destCtor.GetParameters(); + + if (ctorParameters.Length == 0 || !options.ConstructorMappingEnabled) + return false; + + var ctorMap = new ConstructorMap(destCtor, typeMap); + + foreach (var parameter in ctorParameters) + { + var resolvers = new LinkedList(); + + var canResolve = MapDestinationPropertyToSource(options, sourceTypeInfo, destCtor.DeclaringType, parameter.GetType(), parameter.Name, resolvers); + if ((!canResolve && parameter.IsOptional) || CtorParamConfigurations.Any(c => c.CheckCtorParamName(parameter.Name))) + { + canResolve = true; + } + ctorMap.AddParameter(parameter, resolvers.ToArray(), canResolve); + } + + typeMap.ConstructorMap = ctorMap; + + return ctorMap.CanResolve; + } + + private bool MapDestinationPropertyToSource(ProfileMap options, TypeDetails sourceTypeInfo, Type destType, Type destMemberType, string destMemberInfo, LinkedList members) + { + if (string.IsNullOrEmpty(destMemberInfo)) + { + return false; + } + return options.MemberConfigurations.Any(_ => _.MapDestinationPropertyToSource(options, sourceTypeInfo, destType, destMemberType, destMemberInfo, members)); + } + protected IEnumerable MapToSourceMembers() => MemberConfigurations.Where(m => m.SourceExpression != null && m.SourceExpression.Body == m.SourceExpression.Parameters[0]); diff --git a/src/AutoMapper/ConstructorMap.cs b/src/AutoMapper/ConstructorMap.cs index 99e2595f7c..a11e926da6 100644 --- a/src/AutoMapper/ConstructorMap.cs +++ b/src/AutoMapper/ConstructorMap.cs @@ -4,6 +4,7 @@ using System.Linq.Expressions; using System.Reflection; using AutoMapper.Execution; +using AutoMapper.Configuration; using AutoMapper.QueryableExtensions; using AutoMapper.QueryableExtensions.Impl; diff --git a/src/AutoMapper/ProfileMap.cs b/src/AutoMapper/ProfileMap.cs index f20a551580..b2c65db688 100644 --- a/src/AutoMapper/ProfileMap.cs +++ b/src/AutoMapper/ProfileMap.cs @@ -32,8 +32,8 @@ public ProfileMap(IProfileConfiguration profile, IConfiguration configuration) EnableNullPropagationForQueryMapping = profile.EnableNullPropagationForQueryMapping ?? configuration?.EnableNullPropagationForQueryMapping ?? false; ConstructorMappingEnabled = profile.ConstructorMappingEnabled ?? configuration?.ConstructorMappingEnabled ?? true; ShouldMapField = profile.ShouldMapField ?? configuration?.ShouldMapField ?? (p => p.IsPublic()); - ShouldMapProperty = profile.ShouldMapProperty ?? configuration?.ShouldMapProperty ?? (p => p.IsPublic()); - ShouldMapMethod = profile.ShouldMapMethod ?? configuration?.ShouldMapMethod ?? (p => true); + ShouldMapProperty = profile.ShouldMapProperty ?? configuration?.ShouldMapProperty ?? (p => p.IsPublic()); + ShouldMapMethod = profile.ShouldMapMethod ?? configuration?.ShouldMapMethod ?? (p => true); ShouldUseConstructor = profile.ShouldUseConstructor ?? configuration?.ShouldUseConstructor ?? (c => true); ValueTransformers = profile.ValueTransformers.Concat(configuration?.ValueTransformers ?? Enumerable.Empty()).ToArray(); @@ -75,10 +75,10 @@ public ProfileMap(IProfileConfiguration profile, IConfiguration configuration) public bool EnableNullPropagationForQueryMapping { get; } public string Name { get; } public Func ShouldMapField { get; } - public Func ShouldMapProperty { get; } - public Func ShouldMapMethod { get; } + public Func ShouldMapProperty { get; } + public Func ShouldMapMethod { get; } public Func ShouldUseConstructor { get; } - + public IEnumerable> AllPropertyMapActions { get; } public IEnumerable> AllTypeMapActions { get; } public IEnumerable GlobalIgnores { get; } @@ -156,7 +156,7 @@ private void Configure(TypeMap typeMap, IConfigurationProvider configurationProv } ApplyBaseMaps(typeMap, typeMap, configurationProvider); - ApplyDerivedMaps(typeMap, typeMap, configurationProvider); + ApplyDerivedMaps(typeMap, typeMap, configurationProvider); ApplyMemberMaps(typeMap, configurationProvider); } @@ -209,9 +209,9 @@ private void ApplyBaseMaps(TypeMap derivedMap, TypeMap currentMap, IConfiguratio } private void ApplyMemberMaps(TypeMap mainMap, IConfigurationProvider configurationProvider) - { - AddMemberMaps(mainMap.IncludedMembers, mainMap, configurationProvider); - AddMemberMaps(mainMap.GetUntypedIncludedMembers(), mainMap, configurationProvider); + { + AddMemberMaps(mainMap.IncludedMembers, mainMap, configurationProvider); + AddMemberMaps(mainMap.GetUntypedIncludedMembers(), mainMap, configurationProvider); } private void AddMemberMaps(LambdaExpression[] includedMembers, TypeMap mainMap, IConfigurationProvider configurationProvider) @@ -226,15 +226,16 @@ private void ApplyDerivedMaps(TypeMap baseMap, TypeMap typeMap, IConfigurationPr { foreach (var derivedMap in configurationProvider.GetIncludedTypeMaps(typeMap.IncludedDerivedTypes)) { - derivedMap.IncludeBaseTypes(typeMap.SourceType, typeMap.DestinationType); + derivedMap.IncludeBaseTypes(typeMap.SourceType, typeMap.DestinationType); derivedMap.AddInheritedMap(baseMap); ApplyDerivedMaps(baseMap, derivedMap, configurationProvider); } } - } + } + public readonly struct IncludedMember - { + { public IncludedMember(TypeMap typeMap, LambdaExpression memberExpression) { TypeMap = typeMap; diff --git a/src/AutoMapper/TypeMapFactory.cs b/src/AutoMapper/TypeMapFactory.cs index 36c4b87a04..3020272849 100644 --- a/src/AutoMapper/TypeMapFactory.cs +++ b/src/AutoMapper/TypeMapFactory.cs @@ -24,52 +24,52 @@ public TypeMap CreateTypeMap(Type sourceType, Type destinationType, ProfileMap o typeMap.AddPropertyMap(destProperty, resolvers); } } - if (!destinationType.IsAbstract()) - { - foreach (var destCtor in destTypeInfo.Constructors.OrderByDescending(ci => ci.GetParameters().Length)) - { - if (MapDestinationCtorToSource(typeMap, destCtor, sourceTypeInfo, options)) - { - break; - } - } - } + //if (!destinationType.IsAbstract()) + //{ + // foreach (var destCtor in destTypeInfo.Constructors.OrderByDescending(ci => ci.GetParameters().Length)) + // { + // if (MapDestinationCtorToSource(typeMap, destCtor, sourceTypeInfo, options)) + // { + // break; + // } + // } + //} return typeMap; } private bool MapDestinationPropertyToSource(ProfileMap options, TypeDetails sourceTypeInfo, Type destType, Type destMemberType, string destMemberInfo, LinkedList members) { - if(string.IsNullOrEmpty(destMemberInfo)) + if (string.IsNullOrEmpty(destMemberInfo)) { return false; } return options.MemberConfigurations.Any(_ => _.MapDestinationPropertyToSource(options, sourceTypeInfo, destType, destMemberType, destMemberInfo, members)); } - private bool MapDestinationCtorToSource(TypeMap typeMap, ConstructorInfo destCtor, TypeDetails sourceTypeInfo, ProfileMap options) - { - var ctorParameters = destCtor.GetParameters(); + //private bool MapDestinationCtorToSource(TypeMap typeMap, ConstructorInfo destCtor, TypeDetails sourceTypeInfo, ProfileMap options) + //{ + // var ctorParameters = destCtor.GetParameters(); - if (ctorParameters.Length == 0 || !options.ConstructorMappingEnabled) - return false; + // if (ctorParameters.Length == 0 || !options.ConstructorMappingEnabled) + // return false; - var ctorMap = new ConstructorMap(destCtor, typeMap); + // var ctorMap = new ConstructorMap(destCtor, typeMap, options); - foreach (var parameter in ctorParameters) - { - var resolvers = new LinkedList(); + // foreach (var parameter in ctorParameters) + // { + // var resolvers = new LinkedList(); - var canResolve = MapDestinationPropertyToSource(options, sourceTypeInfo, destCtor.DeclaringType, parameter.GetType(), parameter.Name, resolvers); - if(!canResolve && parameter.IsOptional) - { - canResolve = true; - } - ctorMap.AddParameter(parameter, resolvers.ToArray(), canResolve); - } + // var canResolve = MapDestinationPropertyToSource(options, sourceTypeInfo, destCtor.DeclaringType, parameter.GetType(), parameter.Name, resolvers); + // if((!canResolve && parameter.IsOptional) || ctorMap.ContainsCtorParameterConfig(parameter.Name)) + // { + // canResolve = true; + // } + // ctorMap.AddParameter(parameter, resolvers.ToArray(), canResolve); + // } - typeMap.ConstructorMap = ctorMap; + // typeMap.ConstructorMap = ctorMap; - return ctorMap.CanResolve; - } + // return ctorMap.CanResolve; + //} } } \ No newline at end of file diff --git a/src/UnitTests/Constructors.cs b/src/UnitTests/Constructors.cs index ed2a61b68d..473f8f8ab3 100644 --- a/src/UnitTests/Constructors.cs +++ b/src/UnitTests/Constructors.cs @@ -1609,4 +1609,133 @@ public void Should_redirect_value() dest.Value1.ShouldBeNull(); } } + + public class When_configuring_ctor_param_members_without_source_property_1 : AutoMapperSpecBase + { + public class Source + { + public string Result { get; } + + public Source(string result) + { + Result = result; + } + } + + public class Dest + { + public string Result{ get; } + public dynamic Details { get; } + + public Dest(string result, DestInner1 inner1) + { + Result = result; + Details = inner1; + } + public Dest(string result, DestInner2 inner2) + { + Result = result; + Details = inner2; + } + + public class DestInner1 + { + public int Value { get; } + + public DestInner1(int value) + { + Value = value; + } + } + + public class DestInner2 + { + public int Value { get; } + + public DestInner2(int value) + { + Value = value; + } + } + } + + protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(config => + { + config.CreateMap() + .ForCtorParam("inner1", cfg => cfg.MapFrom(_ => new Dest.DestInner1(100))); + }); + + [Fact] + public void Should_redirect_value() + { + var dest = Mapper.Map(new Source("Success")); + + dest.ShouldNotBeNull(); + } + } + + public class When_configuring_ctor_param_members_without_source_property_2 : AutoMapperSpecBase + { + public class Source + { + public string Result { get; } + + public Source(string result) + { + Result = result; + } + } + + public class Dest + { + public string Result{ get; } + public dynamic Details { get; } + + public Dest(string result, DestInner1 inner1) + { + Result = result; + Details = inner1; + } + public Dest(string result, DestInner2 inner2) + { + Result = result; + Details = inner2; + } + + public class DestInner1 + { + public int Value { get; } + + public DestInner1(int value) + { + Value = value; + } + } + + public class DestInner2 + { + public int Value { get; } + + public DestInner2(int value) + { + Value = value; + } + } + } + + protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(config => + { + config.CreateMap() + .ForCtorParam("inner2", cfg => cfg.MapFrom(_ => new Dest.DestInner2(100))); + }); + + [Fact] + public void Should_redirect_value() + { + var dest = Mapper.Map(new Source("Success")); + + dest.ShouldNotBeNull(); + } + } + }