Skip to content
9 changes: 8 additions & 1 deletion docs/Construction.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
```
10 changes: 8 additions & 2 deletions src/AutoMapper/Configuration/IProfileConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public interface IProfileConfiguration

/// <summary>
/// Specify which properties should be mapped.
/// By default only public properties are mapped.e
/// By default only public properties are mapped.
/// </summary>
Func<PropertyInfo, bool> ShouldMapProperty { get; }

Expand All @@ -39,6 +39,12 @@ public interface IProfileConfiguration
/// </summary>
Func<FieldInfo, bool> ShouldMapField { get; }

/// <summary>
/// Specify which constructors should be considered for the destination objects.
/// By default all constructors are considered.
/// </summary>
Func<ConstructorInfo, bool> ShouldUseConstructor { get; }

string ProfileName { get; }
IEnumerable<string> GlobalIgnores { get; }
INamingConvention SourceMemberNamingConvention { get; }
Expand All @@ -47,4 +53,4 @@ public interface IProfileConfiguration
IEnumerable<ITypeMapConfiguration> OpenTypeMapConfigs { get; }
IEnumerable<ValueTransformerConfiguration> ValueTransformers { get; }
}
}
}
2 changes: 2 additions & 0 deletions src/AutoMapper/IProfileExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ public interface IProfileExpression

Func<PropertyInfo, bool> ShouldMapProperty { get; set; }
Func<FieldInfo, bool> ShouldMapField { get; set; }
Func<ConstructorInfo, bool> ShouldUseConstructor { get; set; }

string ProfileName { get; }
IMemberConfiguration AddMemberConfiguration();
IConditionalObjectMapper AddConditionalObjectMapper();
Expand Down
1 change: 1 addition & 0 deletions src/AutoMapper/Profile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ IEnumerable<Action<PropertyMap, IMemberConfigurationExpression>> IProfileConfigu
public bool? EnableNullPropagationForQueryMapping { get; set; }
public Func<PropertyInfo, bool> ShouldMapProperty { get; set; }
public Func<FieldInfo, bool> ShouldMapField { get; set; }
public Func<ConstructorInfo, bool> ShouldUseConstructor { get; set; }

public INamingConvention SourceMemberNamingConvention { get; set; }
public INamingConvention DestinationMemberNamingConvention { get; set; }
Expand Down
42 changes: 22 additions & 20 deletions src/AutoMapper/ProfileMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -83,6 +84,7 @@ public ProfileMap(IProfileConfiguration profile, IConfiguration configuration)
public string Name { get; }
public Func<FieldInfo, bool> ShouldMapField { get; }
public Func<PropertyInfo, bool> ShouldMapProperty { get; }
public Func<ConstructorInfo, bool> ShouldUseConstructor { get; }

public IEnumerable<Action<PropertyMap, IMemberConfigurationExpression>> AllPropertyMapActions { get; }
public IEnumerable<Action<TypeMap, IMappingExpression>> AllTypeMapActions { get; }
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand All @@ -177,8 +179,8 @@ public TypeMap CreateConventionTypeMap(TypePair types, IConfigurationProvider co

config.Configure(typeMap);

Configure(typeMap, configurationProvider);

Configure(typeMap, configurationProvider);
return typeMap;
}

Expand All @@ -188,8 +190,8 @@ public TypeMap CreateInlineMap(TypePair types, IConfigurationProvider configurat

typeMap.IsConventionMap = true;

Configure(typeMap, configurationProvider);

Configure(typeMap, configurationProvider);
return typeMap;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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<TypeMap> GetIncludedTypeMaps(IEnumerable<TypePair> 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;
}
}
}
}
}
13 changes: 10 additions & 3 deletions src/AutoMapper/TypeDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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() });
Expand Down Expand Up @@ -59,7 +59,9 @@ private static IEnumerable<string> PostFixes(IEnumerable<string> postfixes, stri
.Select(postfix => name.Remove(name.Length - postfix.Length));
}

private static Func<MemberInfo, bool> MembersToMap(Func<PropertyInfo, bool> shouldMapProperty, Func<FieldInfo, bool> shouldMapField)
private static Func<MemberInfo, bool> MembersToMap(
Func<PropertyInfo, bool> shouldMapProperty,
Func<FieldInfo, bool> shouldMapField)
{
return m =>
{
Expand All @@ -70,7 +72,7 @@ private static Func<MemberInfo, bool> MembersToMap(Func<PropertyInfo, bool> 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.");
}
};
}
Expand Down Expand Up @@ -163,6 +165,11 @@ private IEnumerable<MemberInfo> GetAllPublicReadableMembers(Func<MemberInfo, boo

private IEnumerable<MemberInfo> GetAllPublicWritableMembers(Func<MemberInfo, bool> membersToMap)
=> GetAllPublicMembers(PropertyWritable, FieldWritable, membersToMap);

private IEnumerable<ConstructorInfo> GetAllConstructors(Func<ConstructorInfo, bool> shouldUseConstructor)
{
return Type.GetDeclaredConstructors().Where(shouldUseConstructor).ToArray();
}

private static bool PropertyReadable(PropertyInfo propertyInfo) => propertyInfo.CanRead;

Expand Down
6 changes: 2 additions & 4 deletions src/AutoMapper/TypeMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down
160 changes: 160 additions & 0 deletions src/UnitTests/ShouldUseConstructor.cs
Original file line number Diff line number Diff line change
@@ -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<Source, Destination>();
});

[Fact]
public void Should_only_map_internal_ctor()
{
Should.Throw<AutoMapperConfigurationException>(() =>
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<Source, Destination>();
});

[Fact]
public void Should_only_map_private_ctor()
{
Should.Throw<AutoMapperConfigurationException>(() =>
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<Source, Destination>();
});

[Fact]
public void Should_only_map_public_ctor()
{
Should.Throw<AutoMapperConfigurationException>(() =>
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<Source, Destination>(); });
}
}