diff --git a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs index 259438a8c..b7bc09b5d 100644 --- a/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs +++ b/AgileMapper.UnitTests/Configuration/WhenConfiguringDataSources.cs @@ -1161,6 +1161,29 @@ public void ShouldAllowIdAndIdentifierConfiguration() } } + // See https://github.com/agileobjects/AgileMapper/issues/146 + [Fact] + public void ShouldApplyAConfiguredSourceInterfaceMember() + { + using (var mapper = Mapper.CreateNew()) + { + mapper.WhenMapping + .From().To() + .Map(ctx => ctx.Source.Empty).To(tgt => tgt.Info); + + var source = new Issue146.Source.Container("12321") { Name = "input" }; + var result = mapper.Map(source).ToANew(); + + result.ShouldNotBeNull(); + result.Name.ShouldBe("input"); + result.Info.ShouldNotBeNull(); + result.Info.Id.ShouldBe("12321"); + + // Source has a .Value member, but we don't runtime-type interfaces + result.Info.Value.ShouldBeNull(); + } + } + // See https://github.com/agileobjects/AgileMapper/issues/64 [Fact] public void ShouldApplyAConfiguredRootSource() @@ -1794,5 +1817,54 @@ public class DataTarget public OtherDataTarget oth; } } + + internal static class Issue146 + { + public static class Source + { + public interface IData + { + string Id { get; set; } + } + + public interface IEmpty : IData { } + + public class Data : IEmpty + { + public string Id { get; set; } + + public string Value => "Data.Value!"; + } + + public class Container + { + public Container(string infoId) + { + Empty = new Data { Id = infoId }; + } + + public string Name { get; set; } + + public IEmpty Empty { get; } + } + } + + public static class Target + { + public class Data + { + public string Id { get; set; } + + public string Value { get; set; } + } + + public class Cont + { + public Data Info { get; set; } + + public string Name { get; set; } + } + } + } } } diff --git a/AgileMapper.UnitTests/WhenViewingMappingPlans.cs b/AgileMapper.UnitTests/WhenViewingMappingPlans.cs index 802d8a5fc..38a754202 100644 --- a/AgileMapper.UnitTests/WhenViewingMappingPlans.cs +++ b/AgileMapper.UnitTests/WhenViewingMappingPlans.cs @@ -327,6 +327,17 @@ public void ShouldNotAttemptUnnecessaryObjectCreationCallbacks() } } + // See https://github.com/agileobjects/AgileMapper/issues/146 + [Fact] + public void ShouldUseBaseInterfaceTypeSourceMembersWithoutRuntimeTyping() + { + string plan = Mapper + .GetPlanFor() + .ToANew(); + + plan.ShouldContain("data.Id = cToCData.Source.Info.Id;"); + } + [Fact] public void ShouldShowEnumMismatches() { @@ -408,5 +419,56 @@ public void ShouldShowAllCachedMappingPlans() mapper.RootMapperCountShouldBe(5); } } + + #region Helper Classes + + internal static class Issue146 + { + public static class Source + { + public interface IData + { + string Id { get; set; } + } + + public interface IEmpty : IData { } + + public class Data : IEmpty + { + public string Id { get; set; } + } + + public class Container + { + public Container(string infoId) + { + Info = new Data { Id = infoId }; + } + + public string Name { get; set; } + + public IEmpty Info { get; } + } + } + + public static class Target + { + public class Data + { + public string Id { get; set; } + + public string Value { get; set; } + } + + public class Cont + { + public Data Info { get; set; } + + public string Name { get; set; } + } + } + } + + #endregion } } diff --git a/AgileMapper/DataSources/Finders/DataSourceFindContext.cs b/AgileMapper/DataSources/Finders/DataSourceFindContext.cs index e5d72f387..27c0a0a01 100644 --- a/AgileMapper/DataSources/Finders/DataSourceFindContext.cs +++ b/AgileMapper/DataSources/Finders/DataSourceFindContext.cs @@ -47,13 +47,11 @@ private IList GetConfiguredDataSources(IMemberMapperData public IDataSource GetFallbackDataSource() => ChildMappingData.RuleSet.FallbackDataSourceFactory.Create(MapperData); - public IDataSource GetFinalDataSource(IDataSource foundDataSource, IChildMemberMappingData mappingData = null) + public IDataSource GetFinalDataSource(IDataSource foundDataSource) + => GetFinalDataSource(foundDataSource, ChildMappingData); + + public IDataSource GetFinalDataSource(IDataSource foundDataSource, IChildMemberMappingData mappingData) { - if (mappingData == null) - { - mappingData = ChildMappingData; - } - var childTargetMember = mappingData.MapperData.TargetMember; if (UseComplexTypeDataSource(foundDataSource, childTargetMember)) diff --git a/AgileMapper/Members/MemberCache.cs b/AgileMapper/Members/MemberCache.cs index 980ce4ee1..c22bbe37d 100644 --- a/AgileMapper/Members/MemberCache.cs +++ b/AgileMapper/Members/MemberCache.cs @@ -1,6 +1,7 @@ namespace AgileObjects.AgileMapper.Members { using System; + using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -33,7 +34,31 @@ public IList GetSourceMembers(Type sourceType) var properties = GetProperties(key.Type, OnlyGettable); var methods = GetMethods(key.Type, OnlyRelevantCallable, Member.GetMethod); - return GetMembers(fields, properties, methods); + var members = new[] { fields, properties, methods }; + + if (!key.Type.IsInterface()) + { + return GetMembers(members); + } + + var interfaceTypes = key.Type.GetAllInterfaces(); + + if (interfaceTypes.Length == 0) + { + return GetMembers(members); + } + + var interfaceCount = interfaceTypes.Length; + + var allMembers = new IEnumerable[3 + interfaceCount]; + allMembers.CopyFrom(members); + + for (var i = 0; i < interfaceCount; ++i) + { + allMembers[i + 3] = GetSourceMembers(interfaceTypes[i]); + } + + return GetMembers(allMembers); }); } @@ -132,12 +157,6 @@ private static bool OnlyCallableSetters(MethodInfo method) #endregion private static IList GetMembers(params IEnumerable[] members) - { - var allMembers = members - .SelectMany(m => m) - .ToArray(); - - return allMembers; - } + => members.SelectMany(m => m).ToArray(); } }