diff --git a/src/AutoMapper/ConstructorMap.cs b/src/AutoMapper/ConstructorMap.cs index f6d9122dbc..a1e80b15d3 100644 --- a/src/AutoMapper/ConstructorMap.cs +++ b/src/AutoMapper/ConstructorMap.cs @@ -66,6 +66,30 @@ public bool ApplyIncludedMember(IncludedMember includedMember) } return canResolve; } + public void ApplyInheritedMap(TypeMap inheritedMap, TypeMap thisMap) + { + if (CanResolve) + { + return; + } + bool canResolve = true; + for (int index = 0; index < _ctorParams.Count; index++) + { + var thisParam = _ctorParams[index]; + if (thisParam.CanResolveValue) + { + continue; + } + var inheritedParam = inheritedMap.ConstructorMap[thisParam.DestinationName]; + if (inheritedParam == null || !inheritedParam.CanResolveValue || thisParam.DestinationType != inheritedParam.DestinationType) + { + canResolve = false; + continue; + } + _ctorParams[index] = new(thisMap, inheritedParam); + } + _canResolve = canResolve; + } } [EditorBrowsable(EditorBrowsableState.Never)] public class ConstructorParameterMap : MemberMap @@ -82,9 +106,11 @@ public ConstructorParameterMap(TypeMap typeMap, ParameterInfo parameter, MemberI SourceMembers = Array.Empty(); } } - public ConstructorParameterMap(ConstructorParameterMap parameterMap, IncludedMember includedMember) : - this(includedMember.TypeMap, parameterMap.Parameter, parameterMap.SourceMembers) => + public ConstructorParameterMap(ConstructorParameterMap parameterMap, IncludedMember includedMember) : this(includedMember.TypeMap, parameterMap) => IncludedMember = includedMember.Chain(parameterMap.IncludedMember); + public ConstructorParameterMap(TypeMap typeMap, ConstructorParameterMap inheritedParameterMap) : + this(typeMap, inheritedParameterMap.Parameter, inheritedParameterMap.SourceMembers) => + Resolver = inheritedParameterMap.Resolver; public ParameterInfo Parameter { get; } public override Type DestinationType => Parameter.ParameterType; public override IncludedMember IncludedMember { get; } diff --git a/src/AutoMapper/TypeMap.cs b/src/AutoMapper/TypeMap.cs index e5e4398539..be936b528a 100644 --- a/src/AutoMapper/TypeMap.cs +++ b/src/AutoMapper/TypeMap.cs @@ -457,6 +457,10 @@ private void ApplyInheritedTypeMap(TypeMap inheritedTypeMap, TypeMap thisMap) { ApplyInheritedPropertyMaps(inheritedTypeMap, thisMap); } + if (inheritedTypeMap.ConstructorMap != null) + { + thisMap.ConstructorMap?.ApplyInheritedMap(inheritedTypeMap, thisMap); + } var inheritedDetails = inheritedTypeMap._details; if (inheritedDetails == null) { diff --git a/src/UnitTests/MappingInheritance/IncludedBaseMappingShouldInheritBaseMappings.cs b/src/UnitTests/MappingInheritance/IncludedBaseMappingShouldInheritBaseMappings.cs index 35579223e7..9244fb7307 100644 --- a/src/UnitTests/MappingInheritance/IncludedBaseMappingShouldInheritBaseMappings.cs +++ b/src/UnitTests/MappingInheritance/IncludedBaseMappingShouldInheritBaseMappings.cs @@ -27,6 +27,66 @@ public class OtherDto public string SubString { get; set; } } + public record class RecordObject(string DifferentBaseString) + { + } + + public record class RecordSubObject(string DifferentBaseString, string SubString) : RecordObject(DifferentBaseString) + { + } + + public record class RecordOtherObject(string BaseString) + { + } + + public record class RecordOtherSubObject(string BaseString, string SubString) : RecordOtherObject(BaseString) + { + } + + public record class RecordOtherSubObjectWithExtraParam(string BaseString, string SubString, string ExtraString) : RecordOtherObject(BaseString) + { + } + + public class ModelObjectWithConstructor + { + public ModelObjectWithConstructor(string onePrime) + { + OnePrime = onePrime; + } + + public string OnePrime { get; } + } + + public class ModelSubObjectWithConstructor : ModelObjectWithConstructor + { + public ModelSubObjectWithConstructor(string onePrime, string two) : base(onePrime) + { + Two = two; + } + + public string Two { get; } + } + + public class DtoObjectWithConstructor + { + public DtoObjectWithConstructor(string one) + { + One = one; + } + + public string One { get; } + } + + public class DtoSubObjectWithConstructorAndWrongType : DtoObjectWithConstructor + { + public DtoSubObjectWithConstructorAndWrongType(int one, string two) : base(one.ToString()) + { + Two = two; + } + + public string Two { get; } + } + [Fact] public void included_mapping_should_inherit_base_mappings_should_not_throw() { @@ -320,6 +380,44 @@ public void include_should_apply_null_substitute() dest.BaseString.ShouldBe("12345"); } + + [Fact] + public void included_mapping_should_inherit_base_constructor_mappings_should_not_throw() + { + var config = new MapperConfiguration(cfg => + { + cfg.ShouldUseConstructor = constructor => constructor.IsPublic; + cfg.CreateMap() + .ForCtorParam(nameof(RecordOtherObject.BaseString), m => m.MapFrom(s => s.DifferentBaseString)) + .Include() + .Include(); + cfg.CreateMap(); + cfg.CreateMap() + .ForCtorParam(nameof(RecordOtherSubObjectWithExtraParam.ExtraString), m => m.MapFrom(s => s.DifferentBaseString + s.SubString)); + }); + config.AssertConfigurationIsValid(); + + var mapper = config.CreateMapper(); + var dest = mapper.Map(new RecordSubObject("base", "sub")); + + dest.BaseString.ShouldBe("base"); + dest.SubString.ShouldBe("sub"); + dest.ExtraString.ShouldBe("basesub"); + } + + [Fact] + public void included_mapping_with_parameter_has_same_name_but_diffent_type_should_throw() + { + var config = new MapperConfiguration(cfg => + { + cfg.CreateMap() + .ForCtorParam("one", m => m.MapFrom(s => s.OnePrime)) + .Include(); + cfg.CreateMap(); + }); + + Assert.Throws(config.AssertConfigurationIsValid).Errors.Single().CanConstruct.ShouldBeFalse(); + } } public class OverrideDifferentMapFrom : AutoMapperSpecBase