Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions src/AutoMapper/ConstructorMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -82,9 +106,11 @@ public ConstructorParameterMap(TypeMap typeMap, ParameterInfo parameter, MemberI
SourceMembers = Array.Empty<MemberInfo>();
}
}
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; }
Expand Down
4 changes: 4 additions & 0 deletions src/AutoMapper/TypeMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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<RecordObject, RecordOtherObject>()
.ForCtorParam(nameof(RecordOtherObject.BaseString), m => m.MapFrom(s => s.DifferentBaseString))
.Include<RecordSubObject, RecordOtherSubObject>()
.Include<RecordSubObject, RecordOtherSubObjectWithExtraParam>();
cfg.CreateMap<RecordSubObject, RecordOtherSubObject>();
cfg.CreateMap<RecordSubObject, RecordOtherSubObjectWithExtraParam>()
.ForCtorParam(nameof(RecordOtherSubObjectWithExtraParam.ExtraString), m => m.MapFrom(s => s.DifferentBaseString + s.SubString));
});
config.AssertConfigurationIsValid();

var mapper = config.CreateMapper();
var dest = mapper.Map<RecordOtherSubObjectWithExtraParam>(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<ModelObjectWithConstructor, DtoObjectWithConstructor>()
.ForCtorParam("one", m => m.MapFrom(s => s.OnePrime))
.Include<ModelSubObjectWithConstructor, DtoSubObjectWithConstructorAndWrongType>();
cfg.CreateMap<ModelSubObjectWithConstructor, DtoSubObjectWithConstructorAndWrongType>();
});

Assert.Throws<AutoMapperConfigurationException>(config.AssertConfigurationIsValid).Errors.Single().CanConstruct.ShouldBeFalse();
}
}

public class OverrideDifferentMapFrom : AutoMapperSpecBase
Expand Down