From 88fb3e0121d9239acfa11a3ac1c32218136c0582 Mon Sep 17 00:00:00 2001 From: bfcamara Date: Tue, 22 Jan 2019 15:58:34 +0000 Subject: [PATCH 1/4] Add support to specify ShouldMapMethod --- .../Configuration/IProfileConfiguration.cs | 7 ++ src/AutoMapper/IProfileExpression.cs | 1 + src/AutoMapper/Profile.cs | 1 + src/AutoMapper/ProfileMap.cs | 6 +- src/AutoMapper/TypeDetails.cs | 15 ++-- src/UnitTests/ShouldMapMethod.cs | 73 +++++++++++++++++++ 6 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 src/UnitTests/ShouldMapMethod.cs diff --git a/src/AutoMapper/Configuration/IProfileConfiguration.cs b/src/AutoMapper/Configuration/IProfileConfiguration.cs index 6be4197de3..30d3f332d1 100644 --- a/src/AutoMapper/Configuration/IProfileConfiguration.cs +++ b/src/AutoMapper/Configuration/IProfileConfiguration.cs @@ -39,6 +39,13 @@ public interface IProfileConfiguration /// Func ShouldMapField { get; } + /// + /// Specify which methods, of those that are eligible (public, parameterless, and non-static or extension methods), should be mapped. + /// By default all eligible methods are mapped. + /// + Func ShouldMapMethod { get; } + + /// /// Specify which constructors should be considered for the destination objects. /// By default all constructors are considered. diff --git a/src/AutoMapper/IProfileExpression.cs b/src/AutoMapper/IProfileExpression.cs index a012df945c..7e3393df15 100644 --- a/src/AutoMapper/IProfileExpression.cs +++ b/src/AutoMapper/IProfileExpression.cs @@ -145,6 +145,7 @@ public interface IProfileExpression Func ShouldMapProperty { get; set; } Func ShouldMapField { get; set; } + Func ShouldMapMethod { get; set; } Func ShouldUseConstructor { get; set; } string ProfileName { get; } diff --git a/src/AutoMapper/Profile.cs b/src/AutoMapper/Profile.cs index cb43a8f06f..c5ba3e9e87 100644 --- a/src/AutoMapper/Profile.cs +++ b/src/AutoMapper/Profile.cs @@ -74,6 +74,7 @@ IEnumerable> IProfileConfigu public bool? EnableNullPropagationForQueryMapping { get; set; } public Func ShouldMapProperty { get; set; } public Func ShouldMapField { get; set; } + public Func ShouldMapMethod { get; set; } public Func ShouldUseConstructor { get; set; } public INamingConvention SourceMemberNamingConvention { get; set; } diff --git a/src/AutoMapper/ProfileMap.cs b/src/AutoMapper/ProfileMap.cs index 35ec0c99ae..ce79ddc9b9 100644 --- a/src/AutoMapper/ProfileMap.cs +++ b/src/AutoMapper/ProfileMap.cs @@ -33,7 +33,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()); + ShouldMapProperty = profile.ShouldMapProperty ?? configuration?.ShouldMapProperty ?? (p => p.IsPublic()); + ShouldMapMethod = profile.ShouldMapMethod ?? configuration?.ShouldMapMethod ?? (p => true); ShouldUseConstructor = profile.ShouldUseConstructor ?? configuration?.ShouldUseConstructor ?? (c => true); CreateMissingTypeMaps = profile.CreateMissingTypeMaps ?? configuration?.CreateMissingTypeMaps ?? true; ValidateInlineMaps = profile.ValidateInlineMaps ?? configuration?.ValidateInlineMaps ?? true; @@ -83,7 +84,8 @@ public ProfileMap(IProfileConfiguration profile, IConfiguration configuration) public bool EnableNullPropagationForQueryMapping { get; } public string Name { get; } public Func ShouldMapField { get; } - public Func ShouldMapProperty { get; } + public Func ShouldMapProperty { get; } + public Func ShouldMapMethod { get; } public Func ShouldUseConstructor { get; } public IEnumerable> AllPropertyMapActions { get; } diff --git a/src/AutoMapper/TypeDetails.cs b/src/AutoMapper/TypeDetails.cs index 9b3bdf165a..e050b1d0d6 100644 --- a/src/AutoMapper/TypeDetails.cs +++ b/src/AutoMapper/TypeDetails.cs @@ -22,9 +22,9 @@ public TypeDetails(Type type, ProfileMap config) var publicWritableMembers = GetAllPublicWritableMembers(membersToMap); PublicReadAccessors = BuildPublicReadAccessors(publicReadableMembers); PublicWriteAccessors = BuildPublicAccessors(publicWritableMembers); - PublicNoArgMethods = BuildPublicNoArgMethods(); + PublicNoArgMethods = BuildPublicNoArgMethods(config.ShouldMapMethod); Constructors = GetAllConstructors(config.ShouldUseConstructor); - PublicNoArgExtensionMethods = BuildPublicNoArgExtensionMethods(config.SourceExtensionMethods); + PublicNoArgExtensionMethods = BuildPublicNoArgExtensionMethods(config.SourceExtensionMethods, config.ShouldMapMethod); AllMembers = PublicReadAccessors.Concat(PublicNoArgMethods).Concat(PublicNoArgExtensionMethods).ToList(); DestinationMemberNames = AllMembers.Select(mi => new DestinationMemberName { Member = mi, Possibles = PossibleNames(mi.Name, config.Prefixes, config.Postfixes).ToArray() }); } @@ -99,9 +99,9 @@ public struct DestinationMemberName public IEnumerable DestinationMemberNames { get; set; } - private IEnumerable BuildPublicNoArgExtensionMethods(IEnumerable sourceExtensionMethodSearch) + private IEnumerable BuildPublicNoArgExtensionMethods(IEnumerable sourceExtensionMethodSearch, Func shouldMapMethod) { - var explicitExtensionMethods = sourceExtensionMethodSearch.Where(method => method.GetParameters()[0].ParameterType == Type); + var explicitExtensionMethods = sourceExtensionMethodSearch.Where(shouldMapMethod).Where(method => method.GetParameters()[0].ParameterType == Type); var genericInterfaces = Type.GetTypeInfo().ImplementedInterfaces.Where(t => t.IsGenericType()); @@ -116,11 +116,11 @@ from genericInterface in genericInterfaces let genericInterfaceArguments = genericInterface.GetTypeInfo().GenericTypeArguments let matchedMethods = ( from extensionMethod in sourceExtensionMethodSearch - where !extensionMethod.IsGenericMethodDefinition + where !extensionMethod.IsGenericMethodDefinition && shouldMapMethod(extensionMethod) select extensionMethod ).Concat( from extensionMethod in sourceExtensionMethodSearch - where extensionMethod.IsGenericMethodDefinition + where extensionMethod.IsGenericMethodDefinition && shouldMapMethod(extensionMethod) && extensionMethod.GetGenericArguments().Length == genericInterfaceArguments.Length select extensionMethod.MakeGenericMethod(genericInterfaceArguments) ) @@ -211,9 +211,10 @@ private IEnumerable GetAllPublicMembers( ); } - private MethodInfo[] BuildPublicNoArgMethods() + private MethodInfo[] BuildPublicNoArgMethods(Func shouldMapMethod) { return Type.GetAllMethods() + .Where(shouldMapMethod) .Where(mi => mi.IsPublic && !mi.IsStatic && mi.DeclaringType != typeof(object)) .Where(m => (m.ReturnType != typeof(void)) && (m.GetParameters().Length == 0)) .ToArray(); diff --git a/src/UnitTests/ShouldMapMethod.cs b/src/UnitTests/ShouldMapMethod.cs new file mode 100644 index 0000000000..30b4b9a9a0 --- /dev/null +++ b/src/UnitTests/ShouldMapMethod.cs @@ -0,0 +1,73 @@ +using Xunit; +using Shouldly; +using System; + +namespace AutoMapper.UnitTests +{ + public class ShouldMapMethod : NonValidatingSpecBase + { + public int SomeValue = 2354; + public int AnotherValue = 6798; + + private Destination _destination; + + class Source + { + private int _someValue; + private int _anotherValue; + + public Source(int someValue, int anotherValue) + { + _someValue = someValue; + anotherValue = _anotherValue; + } + + public int SomeNumber() + { + return _someValue; + } + + public int AnotherNumber() { + return _anotherValue; + } + } + + class Destination + { + public int SomeNumber { get; set; } + public int AnotherNumber { get; set; } + } + + protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg => + { + cfg.ShouldMapMethod = (m => m.Name != nameof(Source.AnotherNumber)); + cfg.CreateMap(); + }); + + protected override void Because_of() + { + _destination = Mapper.Map(new Source(SomeValue, AnotherValue)); + } + + [Fact] + public void Should_report_unmapped_property() + { + new Action(() => Configuration.AssertConfigurationIsValid()) + .ShouldThrowException(ex => + { + ex.Errors.ShouldNotBeNull(); + ex.Errors.ShouldNotBeEmpty(); + ex.Errors[0].UnmappedPropertyNames.ShouldNotBeNull(); + ex.Errors[0].UnmappedPropertyNames.ShouldNotBeNull(); + ex.Errors[0].UnmappedPropertyNames[0].ShouldBe(nameof(Destination.AnotherNumber)); + }); + } + + [Fact] + public void Should_not_map_another_number_method() + { + _destination.SomeNumber.ShouldBe(SomeValue); + _destination.AnotherNumber.ShouldNotBe(AnotherValue); + } + } +} \ No newline at end of file From 7c5782502d1d0f079c4ae8f5e03a24e85b2b5bb2 Mon Sep 17 00:00:00 2001 From: bfcamara Date: Tue, 22 Jan 2019 16:02:53 +0000 Subject: [PATCH 2/4] Fix duplicate assertion --- src/UnitTests/ShouldMapMethod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UnitTests/ShouldMapMethod.cs b/src/UnitTests/ShouldMapMethod.cs index 30b4b9a9a0..b4332648a9 100644 --- a/src/UnitTests/ShouldMapMethod.cs +++ b/src/UnitTests/ShouldMapMethod.cs @@ -58,7 +58,7 @@ public void Should_report_unmapped_property() ex.Errors.ShouldNotBeNull(); ex.Errors.ShouldNotBeEmpty(); ex.Errors[0].UnmappedPropertyNames.ShouldNotBeNull(); - ex.Errors[0].UnmappedPropertyNames.ShouldNotBeNull(); + ex.Errors[0].UnmappedPropertyNames.ShouldNotBeEmpty(); ex.Errors[0].UnmappedPropertyNames[0].ShouldBe(nameof(Destination.AnotherNumber)); }); } From 7d234238cb6ef4424f743a20d3dcda8ab2d46142 Mon Sep 17 00:00:00 2001 From: bfcamara Date: Tue, 22 Jan 2019 18:10:55 +0000 Subject: [PATCH 3/4] CR Feedback: Apply filter ShouldMapMethod to the input param when calling BuildPublicNoArgExtensionMethods --- src/AutoMapper/TypeDetails.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/AutoMapper/TypeDetails.cs b/src/AutoMapper/TypeDetails.cs index e050b1d0d6..68aff98d5f 100644 --- a/src/AutoMapper/TypeDetails.cs +++ b/src/AutoMapper/TypeDetails.cs @@ -24,7 +24,7 @@ public TypeDetails(Type type, ProfileMap config) PublicWriteAccessors = BuildPublicAccessors(publicWritableMembers); PublicNoArgMethods = BuildPublicNoArgMethods(config.ShouldMapMethod); Constructors = GetAllConstructors(config.ShouldUseConstructor); - PublicNoArgExtensionMethods = BuildPublicNoArgExtensionMethods(config.SourceExtensionMethods, config.ShouldMapMethod); + PublicNoArgExtensionMethods = BuildPublicNoArgExtensionMethods(config.SourceExtensionMethods.Where(config.ShouldMapMethod)); AllMembers = PublicReadAccessors.Concat(PublicNoArgMethods).Concat(PublicNoArgExtensionMethods).ToList(); DestinationMemberNames = AllMembers.Select(mi => new DestinationMemberName { Member = mi, Possibles = PossibleNames(mi.Name, config.Prefixes, config.Postfixes).ToArray() }); } @@ -99,9 +99,9 @@ public struct DestinationMemberName public IEnumerable DestinationMemberNames { get; set; } - private IEnumerable BuildPublicNoArgExtensionMethods(IEnumerable sourceExtensionMethodSearch, Func shouldMapMethod) + private IEnumerable BuildPublicNoArgExtensionMethods(IEnumerable sourceExtensionMethodSearch) { - var explicitExtensionMethods = sourceExtensionMethodSearch.Where(shouldMapMethod).Where(method => method.GetParameters()[0].ParameterType == Type); + var explicitExtensionMethods = sourceExtensionMethodSearch.Where(method => method.GetParameters()[0].ParameterType == Type); var genericInterfaces = Type.GetTypeInfo().ImplementedInterfaces.Where(t => t.IsGenericType()); @@ -116,11 +116,11 @@ from genericInterface in genericInterfaces let genericInterfaceArguments = genericInterface.GetTypeInfo().GenericTypeArguments let matchedMethods = ( from extensionMethod in sourceExtensionMethodSearch - where !extensionMethod.IsGenericMethodDefinition && shouldMapMethod(extensionMethod) + where !extensionMethod.IsGenericMethodDefinition select extensionMethod ).Concat( from extensionMethod in sourceExtensionMethodSearch - where extensionMethod.IsGenericMethodDefinition && shouldMapMethod(extensionMethod) + where extensionMethod.IsGenericMethodDefinition && extensionMethod.GetGenericArguments().Length == genericInterfaceArguments.Length select extensionMethod.MakeGenericMethod(genericInterfaceArguments) ) From d95d80d94baf83666a315774a6b80e7c94c8d70a Mon Sep 17 00:00:00 2001 From: bfcamara Date: Tue, 22 Jan 2019 18:11:49 +0000 Subject: [PATCH 4/4] CR Feedback: Add unittest to to ShouldMapMethod in case of extension methods --- src/UnitTests/ShouldMapMethod.cs | 70 +++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/UnitTests/ShouldMapMethod.cs b/src/UnitTests/ShouldMapMethod.cs index b4332648a9..6f404dfb16 100644 --- a/src/UnitTests/ShouldMapMethod.cs +++ b/src/UnitTests/ShouldMapMethod.cs @@ -4,7 +4,7 @@ namespace AutoMapper.UnitTests { - public class ShouldMapMethod : NonValidatingSpecBase + public class ShouldMapMethodInstanceMethods : NonValidatingSpecBase { public int SomeValue = 2354; public int AnotherValue = 6798; @@ -70,4 +70,72 @@ public void Should_not_map_another_number_method() _destination.AnotherNumber.ShouldNotBe(AnotherValue); } } + + + static class SourceExtensions + { + public static int SomeNumber(this ShouldMapMethodExtensionMethods.Source source) + { + return source.SomeValue; + } + + public static int AnotherNumber(this ShouldMapMethodExtensionMethods.Source source) + { + return source.AnotherValue; + } + } + + public class ShouldMapMethodExtensionMethods : NonValidatingSpecBase + { + public int SomeValue = 4698; + public int AnotherValue = 2374; + + private Destination _destination; + + public class Source + { + public int SomeValue { get; set; } + public int AnotherValue { get; set; } + } + + public class Destination + { + public int SomeNumber { get; set; } + public int AnotherNumber { get; set; } + } + + protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg => + { + cfg.IncludeSourceExtensionMethods(typeof(SourceExtensions)); + cfg.ShouldMapMethod = (m => m.Name != nameof(SourceExtensions.AnotherNumber)); + cfg.CreateMap(); + }); + + protected override void Because_of() + { + _destination = Mapper.Map(new Source { SomeValue = SomeValue, AnotherValue = AnotherValue }); + } + + [Fact] + public void Should_report_unmapped_property() + { + new Action(() => Configuration.AssertConfigurationIsValid()) + .ShouldThrowException(ex => + { + ex.Errors.ShouldNotBeNull(); + ex.Errors.ShouldNotBeEmpty(); + ex.Errors[0].UnmappedPropertyNames.ShouldNotBeNull(); + ex.Errors[0].UnmappedPropertyNames.ShouldNotBeEmpty(); + ex.Errors[0].UnmappedPropertyNames[0].ShouldBe(nameof(Destination.AnotherNumber)); + }); + } + + [Fact] + public void Should_not_map_another_number_method() + { + _destination.SomeNumber.ShouldBe(SomeValue); + _destination.AnotherNumber.ShouldNotBe(AnotherValue); + } + } + } \ No newline at end of file