From 7f95158be407be95bf4de85721cd57d7829b56ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stano=20Pe=C5=A5ko?= Date: Sat, 18 Jan 2020 18:53:03 +0100 Subject: [PATCH] UseInterfaceHierarchy configuration --- src/Mapster.Tests/WhenMappingToInterface.cs | 88 ++++++++++++++++++- src/Mapster/Adapters/BaseClassAdapter.cs | 2 +- src/Mapster/Adapters/DictionaryAdapter.cs | 6 +- .../Settings/ValueAccessingStrategy.cs | 8 +- src/Mapster/TypeAdapterSetter.cs | 19 +++- src/Mapster/TypeAdapterSettings.cs | 14 +-- src/Mapster/Utils/ReflectionUtils.cs | 14 +-- 7 files changed, 129 insertions(+), 22 deletions(-) diff --git a/src/Mapster.Tests/WhenMappingToInterface.cs b/src/Mapster.Tests/WhenMappingToInterface.cs index f7c9b33b..f1164dd3 100644 --- a/src/Mapster.Tests/WhenMappingToInterface.cs +++ b/src/Mapster.Tests/WhenMappingToInterface.cs @@ -36,7 +36,7 @@ public void MapToInterface() } [TestMethod] - public void MapToInheritedInterface() + public void MapToInheritedInterface_InterfaceHierarchyEnabled() { var dto = new InheritedDto { @@ -46,7 +46,10 @@ public void MapToInheritedInterface() UnmappedSource = "Lorem ipsum" }; - IInheritedDto idto = dto.Adapt(); + var config = new TypeAdapterConfig(); + config.Default.UseInterfaceHierarchy(true); + + IInheritedDto idto = dto.Adapt(config); idto.ShouldNotBeNull(); idto.ShouldSatisfyAllConditions( @@ -57,6 +60,79 @@ public void MapToInheritedInterface() ); } + [TestMethod] + public void MapToInheritedInterface_InterfaceHierarchyDisabled() + { + var dto = new InheritedDto + { + Id = 1, + Name = "Test", + DateOfBirth = new DateTime(1978, 12, 10), + UnmappedSource = "Lorem ipsum" + }; + + var config = new TypeAdapterConfig(); + + IInheritedDto idto = dto.Adapt(config); + + idto.ShouldNotBeNull(); + idto.ShouldSatisfyAllConditions( + () => idto.Id.ShouldBe(default), + () => idto.Name.ShouldBeNull(), + () => idto.DateOfBirth.ShouldBe(dto.DateOfBirth), + () => idto.UnmappedTarget.ShouldBeNull() + ); + } + + [TestMethod] + public void MapToInstanceWithInterface_InterfaceHierarchyEnabled() + { + var dto = new InheritedDto + { + Id = 1, + Name = "Test", + DateOfBirth = new DateTime(1978, 12, 10), + UnmappedSource = "Lorem ipsum" + }; + + var config = new TypeAdapterConfig(); + config.Default.UseInterfaceHierarchy(true); + + IInheritedDto target = new ImplementedDto(); + dto.Adapt(target, config); + + target.ShouldNotBeNull(); + target.ShouldSatisfyAllConditions( + () => target.Id.ShouldBe(dto.Id), + () => target.Name.ShouldBe(dto.Name), + () => target.DateOfBirth.ShouldBe(dto.DateOfBirth), + () => target.UnmappedTarget.ShouldBeNull() + ); + } + + [TestMethod] + public void MapToInstanceWithInterface_InterfaceHierarchyDisabled() + { + var dto = new InheritedDto + { + Id = 1, + Name = "Test", + DateOfBirth = new DateTime(1978, 12, 10), + UnmappedSource = "Lorem ipsum" + }; + + IInheritedDto target = new ImplementedDto(); + dto.Adapt(target); + + target.ShouldNotBeNull(); + target.ShouldSatisfyAllConditions( + () => target.Id.ShouldBe(default), + () => target.Name.ShouldBeNull(), + () => target.DateOfBirth.ShouldBe(dto.DateOfBirth), + () => target.UnmappedTarget.ShouldBeNull() + ); + } + [TestMethod] public void MapToComplexInterface() { @@ -202,6 +278,14 @@ public class InheritedDto public string UnmappedSource { get; set; } } + public class ImplementedDto : IInheritedDto + { + public int Id { get; set; } + public string Name { get; set; } + public DateTime DateOfBirth { get; set; } + public string UnmappedTarget { get; set; } + } + public interface IComplexInterface { string Name { get; set; } diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index 2258958f..14362baa 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -171,7 +171,7 @@ protected virtual ClassModel GetSetterModel(CompileArgument arg) { return new ClassModel { - Members = arg.DestinationType.GetFieldsAndProperties(requireSetter: true, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public) + Members = arg.DestinationType.GetFieldsAndProperties(useInterfaceHierarchy: arg.Settings.UseInterfaceHierarchy, requireSetter: true, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public) }; } diff --git a/src/Mapster/Adapters/DictionaryAdapter.cs b/src/Mapster/Adapters/DictionaryAdapter.cs index 59c330b5..f1ec5dd6 100644 --- a/src/Mapster/Adapters/DictionaryAdapter.cs +++ b/src/Mapster/Adapters/DictionaryAdapter.cs @@ -26,7 +26,7 @@ protected override bool CanInline(Expression source, Expression? destination, Co return false; //allow inline for dict-to-dict, only when IgnoreNonMapped - return arg.SourceType.GetDictionaryType() == null + return arg.SourceType.GetDictionaryType() == null || arg.Settings.IgnoreNonMapped == true; } @@ -83,7 +83,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio dict.Add(ignore.Key, setWithCondition); } } - + //dict to switch if (dict.Count > 0 || ignores.Count > 0) { @@ -175,7 +175,7 @@ protected override ClassModel GetSetterModel(CompileArgument arg) if (arg.SourceType.GetDictionaryType() == null) { var srcNames = arg.GetSourceNames(); - var propNames = arg.SourceType.GetFieldsAndProperties(accessorFlags: BindingFlags.NonPublic | BindingFlags.Public) + var propNames = arg.SourceType.GetFieldsAndProperties(useInterfaceHierarchy: arg.Settings.UseInterfaceHierarchy, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public) .Where(model => model.ShouldMapMember(arg, MemberSide.Source)) .Select(model => model.Name) .Where(name => !srcNames.Contains(name)) diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index da731c33..59f36703 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -75,7 +75,7 @@ public static class ValueAccessingStrategy private static Expression? PropertyOrFieldFn(Expression source, IMemberModel destinationMember, CompileArgument arg) { - var members = source.Type.GetFieldsAndProperties(accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); + var members = source.Type.GetFieldsAndProperties(useInterfaceHierarchy: arg.Settings.UseInterfaceHierarchy, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); var strategy = arg.Settings.NameMatchingStrategy; var destinationMemberName = destinationMember.GetMemberName(arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter); return members @@ -110,7 +110,7 @@ public static class ValueAccessingStrategy private static Expression? GetDeepFlattening(Expression source, string propertyName, CompileArgument arg) { var strategy = arg.Settings.NameMatchingStrategy; - var members = source.Type.GetFieldsAndProperties(accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); + var members = source.Type.GetFieldsAndProperties(useInterfaceHierarchy: arg.Settings.UseInterfaceHierarchy, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); foreach (var member in members) { if (!member.ShouldMapMember(arg, MemberSide.Source)) @@ -137,7 +137,7 @@ internal static IEnumerable FindUnflatteningPairs(Expression sourc { var strategy = arg.Settings.NameMatchingStrategy; var destinationMemberName = destinationMember.GetMemberName(arg.Settings.GetMemberNames, strategy.DestinationMemberNameConverter); - var members = source.Type.GetFieldsAndProperties(accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); + var members = source.Type.GetFieldsAndProperties(useInterfaceHierarchy: arg.Settings.UseInterfaceHierarchy, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); foreach (var member in members) { @@ -160,7 +160,7 @@ internal static IEnumerable FindUnflatteningPairs(Expression sourc private static IEnumerable GetDeepUnflattening(IMemberModel destinationMember, string propertyName, CompileArgument arg) { var strategy = arg.Settings.NameMatchingStrategy; - var members = destinationMember.Type.GetFieldsAndProperties(accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); + var members = destinationMember.Type.GetFieldsAndProperties(useInterfaceHierarchy: arg.Settings.UseInterfaceHierarchy, accessorFlags: BindingFlags.NonPublic | BindingFlags.Public); foreach (var member in members) { if (!member.ShouldMapMember(arg, MemberSide.Destination)) diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index c2cc75c5..e4a850d3 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -236,6 +236,14 @@ public static class TypeAdapterSetterExtensions setter.Settings.Unflattening = value; return setter; } + + public static TSetter UseInterfaceHierarchy(this TSetter setter, bool value) where TSetter : TypeAdapterSetter + { + setter.CheckCompiled(); + + setter.Settings.UseInterfaceHierarchy = value; + return setter; + } } public class TypeAdapterSetter : TypeAdapterSetter @@ -444,7 +452,7 @@ internal TypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig paren Expression> source, Expression>? shouldMap = null) { this.CheckCompiled(); - + Settings.Resolvers.Add(new InvokerModel { DestinationMemberName = memberName, @@ -553,7 +561,7 @@ internal TypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig paren return this; } - public TypeAdapterSetter Include() + public TypeAdapterSetter Include() where TDerivedSource: class, TSource where TDerivedDestination: class, TDestination { @@ -808,5 +816,12 @@ public TwoWaysTypeAdapterSetter(TypeAdapterConfig config) DestinationToSourceSetter.MaxDepth(value); return this; } + + public TwoWaysTypeAdapterSetter UseInterfaceHierarchy(bool value) + { + SourceToDestinationSetter.UseInterfaceHierarchy(value); + DestinationToSourceSetter.UseInterfaceHierarchy(value); + return this; + } } } diff --git a/src/Mapster/TypeAdapterSettings.cs b/src/Mapster/TypeAdapterSettings.cs index 3a091068..ca79928a 100644 --- a/src/Mapster/TypeAdapterSettings.cs +++ b/src/Mapster/TypeAdapterSettings.cs @@ -1,13 +1,11 @@ -using System; +using Mapster.Models; +using System; using System.Collections.Generic; using System.Linq.Expressions; -using System.Reflection; -using Mapster.Adapters; -using Mapster.Models; namespace Mapster { - public class TypeAdapterSettings: SettingStore + public class TypeAdapterSettings : SettingStore { public IgnoreDictionary Ignore { @@ -69,6 +67,12 @@ public NameMatchingStrategy NameMatchingStrategy set => Set("SkipDestinationMemberCheck", value); } + public bool? UseInterfaceHierarchy + { + get => Get(nameof(UseInterfaceHierarchy)); + set => Set(nameof(UseInterfaceHierarchy), value); + } + public List> ShouldMapMember { get => Get("ShouldMapMember", () => new List>()); diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index 4c253211..1d2eff66 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -79,10 +79,14 @@ public static bool IsPoco(this Type type) if (type.IsConvertible()) return false; - return type.GetFieldsAndProperties(requireSetter: true).Any(); + return type.GetFieldsAndProperties(useInterfaceHierarchy: true, requireSetter: true).Any(); } - public static IEnumerable GetFieldsAndProperties(this Type type, bool requireSetter = false, BindingFlags accessorFlags = BindingFlags.Public) + public static IEnumerable GetFieldsAndProperties( + this Type type, + bool? useInterfaceHierarchy, + bool requireSetter = false, + BindingFlags accessorFlags = BindingFlags.Public) { var bindingFlags = BindingFlags.Instance | accessorFlags; @@ -99,9 +103,9 @@ IEnumerable getFieldsFunc(Type t) => t.GetFields(bindingFlags) IEnumerable fields; #if NETSTANDARD1_3 - if (type.IsInterface()) + if (type.IsInterface() && (useInterfaceHierarchy == true)) #else - if (type.IsInterface) + if (type.IsInterface && (useInterfaceHierarchy == true)) #endif { IEnumerable allInterfaces = GetAllInterfaces(type); @@ -232,7 +236,7 @@ public static bool IsRecordType(this Type type) return false; //no public setter - var props = type.GetFieldsAndProperties().ToList(); + var props = type.GetFieldsAndProperties(useInterfaceHierarchy: true).ToList(); if (props.Any(p => p.SetterModifier == AccessModifier.Public)) return false;