diff --git a/docs/Construction.md b/docs/Construction.md index d2b3676998..77510c2793 100644 --- a/docs/Construction.md +++ b/docs/Construction.md @@ -41,8 +41,15 @@ Mapper.Initialize(cfg => This works for both LINQ projections and in-memory mapping. -You can also disable constructor mapping : +You can also disable constructor mapping: ```c# Mapper.Initialize(cfg => cfg.DisableConstructorMapping()); ``` + +You can configure which constructors are considered for the destination object: + +```c# +// don't map private constructors +Mapper.Initialize(cfg => cfg.ShouldUseConstructor = ci => !ci.IsPrivate); +``` diff --git a/src/AutoMapper/Configuration/IProfileConfiguration.cs b/src/AutoMapper/Configuration/IProfileConfiguration.cs index 10ce0e66d2..6be4197de3 100644 --- a/src/AutoMapper/Configuration/IProfileConfiguration.cs +++ b/src/AutoMapper/Configuration/IProfileConfiguration.cs @@ -29,7 +29,7 @@ public interface IProfileConfiguration /// /// Specify which properties should be mapped. - /// By default only public properties are mapped.e + /// By default only public properties are mapped. /// Func ShouldMapProperty { get; } @@ -39,6 +39,12 @@ public interface IProfileConfiguration /// Func ShouldMapField { get; } + /// + /// Specify which constructors should be considered for the destination objects. + /// By default all constructors are considered. + /// + Func ShouldUseConstructor { get; } + string ProfileName { get; } IEnumerable GlobalIgnores { get; } INamingConvention SourceMemberNamingConvention { get; } @@ -47,4 +53,4 @@ public interface IProfileConfiguration IEnumerable OpenTypeMapConfigs { get; } IEnumerable ValueTransformers { get; } } -} \ No newline at end of file +} diff --git a/src/AutoMapper/IProfileExpression.cs b/src/AutoMapper/IProfileExpression.cs index 8cecf71947..a012df945c 100644 --- a/src/AutoMapper/IProfileExpression.cs +++ b/src/AutoMapper/IProfileExpression.cs @@ -145,6 +145,8 @@ public interface IProfileExpression Func ShouldMapProperty { get; set; } Func ShouldMapField { get; set; } + Func ShouldUseConstructor { get; set; } + string ProfileName { get; } IMemberConfiguration AddMemberConfiguration(); IConditionalObjectMapper AddConditionalObjectMapper(); diff --git a/src/AutoMapper/Profile.cs b/src/AutoMapper/Profile.cs index c7f8c63ba0..cb43a8f06f 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 ShouldUseConstructor { get; set; } public INamingConvention SourceMemberNamingConvention { get; set; } public INamingConvention DestinationMemberNamingConvention { get; set; } diff --git a/src/AutoMapper/ProfileMap.cs b/src/AutoMapper/ProfileMap.cs index f1e409e857..96cfe9f741 100644 --- a/src/AutoMapper/ProfileMap.cs +++ b/src/AutoMapper/ProfileMap.cs @@ -34,6 +34,7 @@ public ProfileMap(IProfileConfiguration profile, IConfiguration configuration) ConstructorMappingEnabled = profile.ConstructorMappingEnabled ?? configuration?.ConstructorMappingEnabled ?? true; ShouldMapField = profile.ShouldMapField ?? configuration?.ShouldMapField ?? (p => p.IsPublic()); ShouldMapProperty = profile.ShouldMapProperty ?? configuration?.ShouldMapProperty ?? (p => p.IsPublic()); + ShouldUseConstructor = profile.ShouldUseConstructor ?? configuration?.ShouldUseConstructor ?? (c => true); CreateMissingTypeMaps = profile.CreateMissingTypeMaps ?? configuration?.CreateMissingTypeMaps ?? true; ValidateInlineMaps = profile.ValidateInlineMaps ?? configuration?.ValidateInlineMaps ?? true; @@ -83,6 +84,7 @@ public ProfileMap(IProfileConfiguration profile, IConfiguration configuration) public string Name { get; } public Func ShouldMapField { get; } public Func ShouldMapProperty { get; } + public Func ShouldUseConstructor { get; } public IEnumerable> AllPropertyMapActions { get; } public IEnumerable> AllTypeMapActions { get; } @@ -124,9 +126,9 @@ public void Configure(IConfigurationProvider configurationProvider) } private void BuildTypeMap(IConfigurationProvider configurationProvider, ITypeMapConfiguration config) - { + { var typeMap = _typeMapFactory.CreateTypeMap(config.SourceType, config.DestinationType, this); - + config.Configure(typeMap); configurationProvider.RegisterTypeMap(typeMap); @@ -164,7 +166,7 @@ private void Configure(TypeMap typeMap, IConfigurationProvider configurationProv ApplyBaseMaps(typeMap, typeMap, configurationProvider); ApplyDerivedMaps(typeMap, typeMap, configurationProvider); } - + public bool IsConventionMap(TypePair types) => TypeConfigurations.Any(c => c.IsMatch(types)); public TypeMap CreateConventionTypeMap(TypePair types, IConfigurationProvider configurationProvider) @@ -177,8 +179,8 @@ public TypeMap CreateConventionTypeMap(TypePair types, IConfigurationProvider co config.Configure(typeMap); - Configure(typeMap, configurationProvider); - + Configure(typeMap, configurationProvider); + return typeMap; } @@ -188,8 +190,8 @@ public TypeMap CreateInlineMap(TypePair types, IConfigurationProvider configurat typeMap.IsConventionMap = true; - Configure(typeMap, configurationProvider); - + Configure(typeMap, configurationProvider); + return typeMap; } @@ -215,7 +217,7 @@ public TypeMap CreateClosedGenericTypeMap(ITypeMapConfiguration openMapConfig, T { var neededParameters = closedMap.DestinationTypeOverride.GetGenericParameters().Length; closedMap.DestinationTypeOverride = closedMap.DestinationTypeOverride.MakeGenericType(closedTypes.DestinationType.GetGenericArguments().Take(neededParameters).ToArray()); - } + } return closedMap; } @@ -244,30 +246,30 @@ private void ApplyBaseMaps(TypeMap derivedMap, TypeMap currentMap, IConfiguratio private void ApplyDerivedMaps(TypeMap baseMap, TypeMap typeMap, IConfigurationProvider configurationProvider) { foreach (var inheritedTypeMap in GetIncludedTypeMaps(typeMap.IncludedDerivedTypes, configurationProvider)) - { - inheritedTypeMap.IncludeBaseTypes(typeMap.SourceType, typeMap.DestinationType); + { + inheritedTypeMap.IncludeBaseTypes(typeMap.SourceType, typeMap.DestinationType); inheritedTypeMap.AddInheritedMap(baseMap); ApplyDerivedMaps(baseMap, inheritedTypeMap, configurationProvider); } - } - + } + private static IEnumerable GetIncludedTypeMaps(IEnumerable includedTypes, IConfigurationProvider configurationProvider) - { + { foreach(var pair in includedTypes) - { - var typeMap = configurationProvider.FindTypeMapFor(pair); + { + var typeMap = configurationProvider.FindTypeMapFor(pair); if(typeMap != null) - { + { yield return typeMap; } - typeMap = configurationProvider.ResolveTypeMap(pair); - // we want the exact map the user included, but we could instantiate an open generic + typeMap = configurationProvider.ResolveTypeMap(pair); + // we want the exact map the user included, but we could instantiate an open generic if(typeMap == null || typeMap.Types != pair || typeMap.IsConventionMap) { throw QueryMapperHelper.MissingMapException(pair); - } + } yield return typeMap; - } + } } } } \ No newline at end of file diff --git a/src/AutoMapper/TypeDetails.cs b/src/AutoMapper/TypeDetails.cs index d4e88d322f..9b3bdf165a 100644 --- a/src/AutoMapper/TypeDetails.cs +++ b/src/AutoMapper/TypeDetails.cs @@ -23,7 +23,7 @@ public TypeDetails(Type type, ProfileMap config) PublicReadAccessors = BuildPublicReadAccessors(publicReadableMembers); PublicWriteAccessors = BuildPublicAccessors(publicWritableMembers); PublicNoArgMethods = BuildPublicNoArgMethods(); - Constructors = type.GetDeclaredConstructors().Where(ci => !ci.IsStatic).ToArray(); + Constructors = GetAllConstructors(config.ShouldUseConstructor); PublicNoArgExtensionMethods = BuildPublicNoArgExtensionMethods(config.SourceExtensionMethods); AllMembers = PublicReadAccessors.Concat(PublicNoArgMethods).Concat(PublicNoArgExtensionMethods).ToList(); DestinationMemberNames = AllMembers.Select(mi => new DestinationMemberName { Member = mi, Possibles = PossibleNames(mi.Name, config.Prefixes, config.Postfixes).ToArray() }); @@ -59,7 +59,9 @@ private static IEnumerable PostFixes(IEnumerable postfixes, stri .Select(postfix => name.Remove(name.Length - postfix.Length)); } - private static Func MembersToMap(Func shouldMapProperty, Func shouldMapField) + private static Func MembersToMap( + Func shouldMapProperty, + Func shouldMapField) { return m => { @@ -70,7 +72,7 @@ private static Func MembersToMap(Func shou case FieldInfo field: return !field.IsStatic && shouldMapField(field); default: - throw new ArgumentException("Should be a field or property."); + throw new ArgumentException("Should be a field or a property."); } }; } @@ -163,6 +165,11 @@ private IEnumerable GetAllPublicReadableMembers(Func GetAllPublicWritableMembers(Func membersToMap) => GetAllPublicMembers(PropertyWritable, FieldWritable, membersToMap); + + private IEnumerable GetAllConstructors(Func shouldUseConstructor) + { + return Type.GetDeclaredConstructors().Where(shouldUseConstructor).ToArray(); + } private static bool PropertyReadable(PropertyInfo propertyInfo) => propertyInfo.CanRead; diff --git a/src/AutoMapper/TypeMap.cs b/src/AutoMapper/TypeMap.cs index 8ef43ffe40..3d25cd6ad2 100644 --- a/src/AutoMapper/TypeMap.cs +++ b/src/AutoMapper/TypeMap.cs @@ -182,10 +182,8 @@ public bool PassesCtorValidation() if (DestinationTypeToUse.IsValueType()) return true; - var constructors = DestinationTypeToUse - .GetDeclaredConstructors() - .Where(ci => !ci.IsStatic); - + var constructors = DestinationTypeDetails.Constructors; + //find a ctor with only optional args var ctorWithOptionalArgs = constructors.FirstOrDefault(c => c.GetParameters().All(p => p.IsOptional)); diff --git a/src/UnitTests/ShouldUseConstructor.cs b/src/UnitTests/ShouldUseConstructor.cs new file mode 100644 index 0000000000..474e56971a --- /dev/null +++ b/src/UnitTests/ShouldUseConstructor.cs @@ -0,0 +1,160 @@ +using System.Linq; +using Shouldly; +using Xunit; + +namespace AutoMapper.UnitTests +{ + public class ShouldUseConstructorInternal : NonValidatingSpecBase + { + class Destination + { + internal Destination(int a, string b) + { + } + + public int A { get; } + + public string B { get; } + + public Destination(int a) + { + + } + + private Destination() + { + } + } + + class Source + { + public int A { get; set; } + } + + protected override MapperConfiguration Configuration { get; } = new MapperConfiguration( + cfg => + { + cfg.ShouldUseConstructor = c => c.IsAssembly; + cfg.CreateMap(); + }); + + [Fact] + public void Should_only_map_internal_ctor() + { + Should.Throw(() => + Configuration.AssertConfigurationIsValid()); + } + } + + public class ShouldUseConstructorPrivate : NonValidatingSpecBase + { + + class Destination + { + private Destination(int a, string b) + { + } + + public int A { get; } + + public string B { get; } + + internal Destination(int a) + { + + } + + public Destination() + { + } + } + + class Source + { + public int A { get; set; } + } + + protected override MapperConfiguration Configuration { get; } = new MapperConfiguration( + cfg => + { + cfg.ShouldUseConstructor = c => c.IsPrivate; + cfg.CreateMap(); + }); + + [Fact] + public void Should_only_map_private_ctor() + { + Should.Throw(() => + Configuration.AssertConfigurationIsValid()); + } + } + + public class ShouldUseConstructorPublic : NonValidatingSpecBase + { + class Destination + { + public Destination(int a, string b) + { + } + + public int A { get; } + + public string B { get; } + + internal Destination(int a) + { + + } + + private Destination() + { + } + } + + class Source + { + public int A { get; set; } + } + + protected override MapperConfiguration Configuration { get; } = new MapperConfiguration( + cfg => + { + cfg.ShouldUseConstructor = c => c.IsPublic; + cfg.CreateMap(); + }); + + [Fact] + public void Should_only_map_public_ctor() + { + Should.Throw(() => + Configuration.AssertConfigurationIsValid()); + } + } + + + public class ShouldUseConstructorDefault : AutoMapperSpecBase + { + class Destination + { + public Destination(int a, string b) + { + } + + public int A { get; } + + public string B { get; } + + private Destination() + { + } + } + + class Source + { + public int A { get; set; } + } + + protected override MapperConfiguration Configuration { get; } = + new MapperConfiguration(cfg => { cfg.CreateMap(); }); + } +}