Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/AutoMapper/ApiCompatBaseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ MembersMustExist : Member 'AutoMapper.TypeMap.AddInheritedMap(AutoMapper.TypeMap
MembersMustExist : Member 'AutoMapper.TypeMap.GetExistingPropertyMapFor(System.Reflection.MemberInfo)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'AutoMapper.TypeMap.IsConventionMap.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'AutoMapper.TypeMap.IsConventionMap.set(System.Boolean)' does not exist in the implementation but it does exist in the contract.
CannotMakeTypeAbstract : Type 'AutoMapper.TypeMapFactory' is abstract in the implementation but is not abstract in the contract.
CannotSealType : Type 'AutoMapper.TypeMapFactory' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
MembersMustExist : Member 'AutoMapper.TypeMapFactory..ctor()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'AutoMapper.ValidationContext..ctor(AutoMapper.TypePair, AutoMapper.PropertyMap, AutoMapper.IObjectMapper)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'AutoMapper.ValidationContext..ctor(AutoMapper.TypePair, AutoMapper.PropertyMap, AutoMapper.TypeMap)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'AutoMapper.ValidationContext.PropertyMap.get()' does not exist in the implementation but it does exist in the contract.
Expand Down Expand Up @@ -191,4 +194,4 @@ MembersMustExist : Member 'AutoMapper.QueryableExtensions.ProjectionExpression.T
MembersMustExist : Member 'AutoMapper.QueryableExtensions.ProjectionExpression.To<TResult>(System.Collections.Generic.IDictionary<System.String, System.Object>, System.Linq.Expressions.Expression<System.Func<TResult, System.Object>>[])' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'AutoMapper.QueryableExtensions.ProjectionExpression.To<TResult>(System.Object)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'AutoMapper.QueryableExtensions.ProjectionExpression.To<TResult>(System.Object, System.String[])' does not exist in the implementation but it does exist in the contract.
Total Issues: 192
Total Issues: 195
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ namespace AutoMapper.Configuration
{
public class CtorParamConfigurationExpression<TSource> : ICtorParamConfigurationExpression<TSource>, ICtorParameterConfiguration
{
private readonly string _ctorParamName;
public string CtorParamName { get; }
private readonly List<Action<ConstructorParameterMap>> _ctorParamActions = new List<Action<ConstructorParameterMap>>();

public CtorParamConfigurationExpression(string ctorParamName) => _ctorParamName = ctorParamName;
public CtorParamConfigurationExpression(string ctorParamName) => CtorParamName = ctorParamName;

public void MapFrom<TMember>(Expression<Func<TSource, TMember>> sourceMember)
{
Expand All @@ -31,10 +31,10 @@ public void Configure(TypeMap typeMap)
throw new AutoMapperConfigurationException($"The type {typeMap.Types.DestinationType.Name} does not have a constructor.\n{typeMap.Types.DestinationType.FullName}");
}

var parameter = ctorParams.SingleOrDefault(p => p.Parameter.Name == _ctorParamName);
var parameter = ctorParams.SingleOrDefault(p => p.Parameter.Name == CtorParamName);
if (parameter == null)
{
throw new AutoMapperConfigurationException($"{typeMap.Types.DestinationType.Name} does not have a constructor with a parameter named '{_ctorParamName}'.\n{typeMap.Types.DestinationType.FullName}");
throw new AutoMapperConfigurationException($"{typeMap.Types.DestinationType.Name} does not have a constructor with a parameter named '{CtorParamName}'.\n{typeMap.Types.DestinationType.FullName}");
}
parameter.CanResolveValue = true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
public interface ICtorParameterConfiguration
{
string CtorParamName { get; }
void Configure(TypeMap typeMap);
}
}
15 changes: 14 additions & 1 deletion src/AutoMapper/Configuration/MappingExpressionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,20 @@ public void Configure(TypeMap typeMap)
}
}

foreach(var action in TypeMapActions)
var destTypeInfo = typeMap.DestinationTypeDetails;
if(!typeMap.DestinationType.IsAbstract())
{
foreach(var destCtor in destTypeInfo.Constructors.OrderByDescending(ci => ci.GetParameters().Length))
{
if(typeMap.Profile.MapDestinationCtorToSource(typeMap, destCtor, typeMap.SourceTypeDetails, CtorParamConfigurations))
{
break;
}
}
}


foreach (var action in TypeMapActions)
{
action(typeMap);
}
Expand Down
41 changes: 38 additions & 3 deletions src/AutoMapper/ProfileMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace AutoMapper
[DebuggerDisplay("{Name}")]
public class ProfileMap
{
private readonly TypeMapFactory _typeMapFactory = new TypeMapFactory();
private readonly IEnumerable<ITypeMapConfiguration> _typeMapConfigs;
private readonly IEnumerable<ITypeMapConfiguration> _openTypeMapConfigs;
private readonly LockingConcurrentDictionary<Type, TypeDetails> _typeDetails;
Expand Down Expand Up @@ -119,7 +118,7 @@ public void Configure(IConfigurationProvider configurationProvider)

private void BuildTypeMap(IConfigurationProvider configurationProvider, ITypeMapConfiguration config)
{
var typeMap = _typeMapFactory.CreateTypeMap(config.SourceType, config.DestinationType, this);
var typeMap = TypeMapFactory.CreateTypeMap(config.SourceType, config.DestinationType, this);

config.Configure(typeMap);

Expand Down Expand Up @@ -162,7 +161,7 @@ private void Configure(TypeMap typeMap, IConfigurationProvider configurationProv

public TypeMap CreateClosedGenericTypeMap(ITypeMapConfiguration openMapConfig, TypePair closedTypes, IConfigurationProvider configurationProvider)
{
var closedMap = _typeMapFactory.CreateTypeMap(closedTypes.SourceType, closedTypes.DestinationType, this);
var closedMap = TypeMapFactory.CreateTypeMap(closedTypes.SourceType, closedTypes.DestinationType, this);
closedMap.IsClosedGeneric = true;
openMapConfig.Configure(closedMap);

Expand Down Expand Up @@ -231,6 +230,42 @@ private void ApplyDerivedMaps(TypeMap baseMap, TypeMap typeMap, IConfigurationPr
ApplyDerivedMaps(baseMap, derivedMap, configurationProvider);
}
}

public bool MapDestinationCtorToSource(TypeMap typeMap, ConstructorInfo destCtor, TypeDetails sourceTypeInfo, List<ICtorParameterConfiguration> ctorParamConfigurations)
{
var ctorParameters = destCtor.GetParameters();

if (ctorParameters.Length == 0 || !ConstructorMappingEnabled)
return false;

var ctorMap = new ConstructorMap(destCtor, typeMap);

foreach (var parameter in ctorParameters)
{
var resolvers = new LinkedList<MemberInfo>();

var canResolve = MapDestinationPropertyToSource(sourceTypeInfo, destCtor.DeclaringType, parameter.GetType(), parameter.Name, resolvers);
if ((!canResolve && parameter.IsOptional) || ctorParamConfigurations.Any(c => c.CtorParamName == parameter.Name))
{
canResolve = true;
}
ctorMap.AddParameter(parameter, resolvers.ToArray(), canResolve);
}

typeMap.ConstructorMap = ctorMap;

return ctorMap.CanResolve;
}

public bool MapDestinationPropertyToSource(TypeDetails sourceTypeInfo, Type destType, Type destMemberType, string destMemberInfo, LinkedList<MemberInfo> members)
{
if (string.IsNullOrEmpty(destMemberInfo))
{
return false;
}
return MemberConfigurations.Any(_ => _.MapDestinationPropertyToSource(this, sourceTypeInfo, destType, destMemberType, destMemberInfo, members));
}

}

public readonly struct IncludedMember
Expand Down
3 changes: 1 addition & 2 deletions src/AutoMapper/QueryableExtensions/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,11 +404,10 @@ public override QueryExpressions GetSubQueryExpression(ExpressionBuilder builder
var properties = letMapInfos.Select(m => m.Property).Concat(GetMemberAccessesVisitor.Retrieve(projection, instanceParameter));

var letType = ProxyGenerator.GetSimilarType(typeof(object), properties);
var typeMapFactory = new TypeMapFactory();
TypeMap firstTypeMap;
lock(_configurationProvider)
{
firstTypeMap = typeMapFactory.CreateTypeMap(request.SourceType, letType, typeMap.Profile);
firstTypeMap = TypeMapFactory.CreateTypeMap(request.SourceType, letType, typeMap.Profile);
}
var secondParameter = Parameter(letType, "dtoLet");

Expand Down
51 changes: 3 additions & 48 deletions src/AutoMapper/TypeMapFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

namespace AutoMapper
{
public class TypeMapFactory
public static class TypeMapFactory
{
public TypeMap CreateTypeMap(Type sourceType, Type destinationType, ProfileMap options)
public static TypeMap CreateTypeMap(Type sourceType, Type destinationType, ProfileMap options)
{
var sourceTypeInfo = options.CreateTypeDetails(sourceType);
var destTypeInfo = options.CreateTypeDetails(destinationType);
Expand All @@ -19,57 +19,12 @@ public TypeMap CreateTypeMap(Type sourceType, Type destinationType, ProfileMap o
{
var resolvers = new LinkedList<MemberInfo>();

if (MapDestinationPropertyToSource(options, sourceTypeInfo, destProperty.DeclaringType, destProperty.GetMemberType(), destProperty.Name, resolvers))
if (options.MapDestinationPropertyToSource(sourceTypeInfo, destProperty.DeclaringType, destProperty.GetMemberType(), destProperty.Name, resolvers))
{
typeMap.AddPropertyMap(destProperty, resolvers);
}
}
if (!destinationType.IsAbstract())
{
foreach (var destCtor in destTypeInfo.Constructors.OrderByDescending(ci => ci.GetParameters().Length))
{
if (MapDestinationCtorToSource(typeMap, destCtor, sourceTypeInfo, options))
{
break;
}
}
}
return typeMap;
}

private bool MapDestinationPropertyToSource(ProfileMap options, TypeDetails sourceTypeInfo, Type destType, Type destMemberType, string destMemberInfo, LinkedList<MemberInfo> members)
{
if(string.IsNullOrEmpty(destMemberInfo))
{
return false;
}
return options.MemberConfigurations.Any(_ => _.MapDestinationPropertyToSource(options, sourceTypeInfo, destType, destMemberType, destMemberInfo, members));
}

private bool MapDestinationCtorToSource(TypeMap typeMap, ConstructorInfo destCtor, TypeDetails sourceTypeInfo, ProfileMap options)
{
var ctorParameters = destCtor.GetParameters();

if (ctorParameters.Length == 0 || !options.ConstructorMappingEnabled)
return false;

var ctorMap = new ConstructorMap(destCtor, typeMap);

foreach (var parameter in ctorParameters)
{
var resolvers = new LinkedList<MemberInfo>();

var canResolve = MapDestinationPropertyToSource(options, sourceTypeInfo, destCtor.DeclaringType, parameter.GetType(), parameter.Name, resolvers);
if(!canResolve && parameter.IsOptional)
{
canResolve = true;
}
ctorMap.AddParameter(parameter, resolvers.ToArray(), canResolve);
}

typeMap.ConstructorMap = ctorMap;

return ctorMap.CanResolve;
}
}
}
131 changes: 131 additions & 0 deletions src/UnitTests/Constructors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1609,4 +1609,135 @@ public void Should_redirect_value()
dest.Value1.ShouldBeNull();
}
}

public class When_configuring_ctor_param_members_without_source_property_1 : AutoMapperSpecBase
{
public class Source
{
public string Result { get; }

public Source(string result)
{
Result = result;
}
}

public class Dest
{
public string Result{ get; }
public dynamic Details { get; }

public Dest(string result, DestInner1 inner1)
{
Result = result;
Details = inner1;
}
public Dest(string result, DestInner2 inner2)
{
Result = result;
Details = inner2;
}

public class DestInner1
{
public int Value { get; }

public DestInner1(int value)
{
Value = value;
}
}

public class DestInner2
{
public int Value { get; }

public DestInner2(int value)
{
Value = value;
}
}
}

protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(config =>
{
config.CreateMap<Source, Dest>()
.ForCtorParam("inner1", cfg => cfg.MapFrom(_ => new Dest.DestInner1(100)));
});

[Fact]
public void Should_redirect_value()
{
var dest = Mapper.Map<Dest>(new Source("Success"));

dest.ShouldNotBeNull();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert here what the property values should be. The same for the other test.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Assert.Equal("100", dest.Details.Value.ToString());
}
}

public class When_configuring_ctor_param_members_without_source_property_2 : AutoMapperSpecBase
{
public class Source
{
public string Result { get; }

public Source(string result)
{
Result = result;
}
}

public class Dest
{
public string Result{ get; }
public dynamic Details { get; }

public Dest(string result, DestInner1 inner1)
{
Result = result;
Details = inner1;
}
public Dest(string result, DestInner2 inner2)
{
Result = result;
Details = inner2;
}

public class DestInner1
{
public int Value { get; }

public DestInner1(int value)
{
Value = value;
}
}

public class DestInner2
{
public int Value { get; }

public DestInner2(int value)
{
Value = value;
}
}
}

protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(config =>
{
config.CreateMap<Source, Dest>()
.ForCtorParam("inner2", cfg => cfg.MapFrom(_ => new Dest.DestInner2(100)));
});

[Fact]
public void Should_redirect_value()
{
var dest = Mapper.Map<Dest>(new Source("Success"));

dest.ShouldNotBeNull();
Assert.Equal("100", dest.Details.Value.ToString());
}
}

}
Loading