Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Summary of changes:
 - Added code to TypeMapFactory+ConstructorMap to check for valid ForCtorParam entries to match against a Constructor

#3159
  • Loading branch information
Mark Quennell committed Jul 8, 2019
commit 484a298e173f0037c202c71ae6a6ca711109bb10
4 changes: 3 additions & 1 deletion src/AutoMapper/ApiCompatBaseline.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Compat issues with assembly AutoMapper:
MembersMustExist : Member 'AutoMapper.AutoMapperConfigurationException.PropertyMap.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'AutoMapper.AutoMapperConfigurationException.PropertyMap.set(AutoMapper.PropertyMap)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'AutoMapper.ConstructorMap..ctor(System.Reflection.ConstructorInfo, AutoMapper.TypeMap)' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'AutoMapper.CtorParamConfigurationExpression<TSource>' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'AutoMapper.IConfigurationProvider.Features' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'AutoMapper.IConfigurationProvider.Features.get()' is present in the implementation but not in the contract.
Expand Down Expand Up @@ -156,6 +157,7 @@ MembersMustExist : Member 'AutoMapper.Configuration.IProfileConfiguration.Valida
InterfacesShouldHaveSameMembers : Interface member 'AutoMapper.Configuration.IPropertyMapConfiguration.SourceExpression' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'AutoMapper.Configuration.IPropertyMapConfiguration.GetDestinationExpression()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'AutoMapper.Configuration.IPropertyMapConfiguration.SourceExpression.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'AutoMapper.Configuration.ITypeMapConfiguration.GetCtorParameterConfigs()' is present in the implementation but not in the contract.
MembersMustExist : Member 'AutoMapper.Configuration.MapperConfigurationExpression.AddProfiles(System.Collections.Generic.IEnumerable<System.Reflection.Assembly>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'AutoMapper.Configuration.MapperConfigurationExpression.AddProfiles(System.Collections.Generic.IEnumerable<System.String>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'AutoMapper.Configuration.MapperConfigurationExpression.AddProfiles(System.Collections.Generic.IEnumerable<System.Type>)' does not exist in the implementation but it does exist in the contract.
Expand Down Expand Up @@ -191,4 +193,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: 194
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,10 @@ public void Configure(TypeMap typeMap)
action(parameter);
}
}

public bool CheckCtorParamName(string paramName)
{
return _ctorParamName == paramName;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
public interface ICtorParameterConfiguration
{
void Configure(TypeMap typeMap);
bool CheckCtorParamName(string paramName);
}
}
2 changes: 2 additions & 0 deletions src/AutoMapper/Configuration/ITypeMapConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;

namespace AutoMapper.Configuration
{
public interface ITypeMapConfiguration
{
void Configure(TypeMap typeMap);
IList<ICtorParameterConfiguration> GetCtorParameterConfigs();
Type SourceType { get; }
Type DestinationType { get; }
bool IsOpenGeneric { get; }
Expand Down
2 changes: 2 additions & 0 deletions src/AutoMapper/Configuration/MappingExpressionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ protected IPropertyMapConfiguration GetDestinationMemberConfiguration(MemberInfo
MemberConfigurations.FirstOrDefault(m => m.DestinationMember.Name == destinationMember.Name);

protected abstract void IgnoreDestinationMember(MemberInfo property, bool ignorePaths = true);

public IList<ICtorParameterConfiguration> GetCtorParameterConfigs() => CtorParamConfigurations;
}

public abstract class MappingExpressionBase<TSource, TDestination, TMappingExpression>
Expand Down
10 changes: 9 additions & 1 deletion src/AutoMapper/ConstructorMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper.Execution;
using AutoMapper.Configuration;
using AutoMapper.QueryableExtensions;
using AutoMapper.QueryableExtensions.Impl;

Expand All @@ -14,15 +15,17 @@ namespace AutoMapper
public class ConstructorMap
{
private readonly IList<ConstructorParameterMap> _ctorParams = new List<ConstructorParameterMap>();
private readonly IList<ICtorParameterConfiguration> _ctorConfigs = new List<ICtorParameterConfiguration>();

public ConstructorInfo Ctor { get; }
public TypeMap TypeMap { get; }
public IEnumerable<ConstructorParameterMap> CtorParams => _ctorParams;

public ConstructorMap(ConstructorInfo ctor, TypeMap typeMap)
public ConstructorMap(ConstructorInfo ctor, TypeMap typeMap, ProfileMap options)
{
Ctor = ctor;
TypeMap = typeMap;
_ctorConfigs = options.GetCtorParameterConfigs(typeMap);
}

private static readonly IExpressionResultConverter[] ExpressionResultConverters =
Expand Down Expand Up @@ -54,5 +57,10 @@ public void AddParameter(ParameterInfo parameter, MemberInfo[] resolvers, bool c
{
_ctorParams.Add(new ConstructorParameterMap(TypeMap, parameter, resolvers, canResolve));
}

public bool ContainsCtorParameterConfig(string paramName)
{
return _ctorConfigs.Any(c => c.CheckCtorParamName(paramName));
}
}
}
38 changes: 26 additions & 12 deletions src/AutoMapper/ProfileMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,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());
ShouldMapMethod = profile.ShouldMapMethod ?? configuration?.ShouldMapMethod ?? (p => true);
ShouldMapProperty = profile.ShouldMapProperty ?? configuration?.ShouldMapProperty ?? (p => p.IsPublic());
ShouldMapMethod = profile.ShouldMapMethod ?? configuration?.ShouldMapMethod ?? (p => true);
ShouldUseConstructor = profile.ShouldUseConstructor ?? configuration?.ShouldUseConstructor ?? (c => true);

ValueTransformers = profile.ValueTransformers.Concat(configuration?.ValueTransformers ?? Enumerable.Empty<ValueTransformerConfiguration>()).ToArray();
Expand Down Expand Up @@ -75,10 +75,10 @@ public ProfileMap(IProfileConfiguration profile, IConfiguration configuration)
public bool EnableNullPropagationForQueryMapping { get; }
public string Name { get; }
public Func<FieldInfo, bool> ShouldMapField { get; }
public Func<PropertyInfo, bool> ShouldMapProperty { get; }
public Func<MethodInfo, bool> ShouldMapMethod { get; }
public Func<PropertyInfo, bool> ShouldMapProperty { get; }
public Func<MethodInfo, bool> ShouldMapMethod { get; }
public Func<ConstructorInfo, bool> ShouldUseConstructor { get; }

public IEnumerable<Action<PropertyMap, IMemberConfigurationExpression>> AllPropertyMapActions { get; }
public IEnumerable<Action<TypeMap, IMappingExpression>> AllTypeMapActions { get; }
public IEnumerable<string> GlobalIgnores { get; }
Expand Down Expand Up @@ -156,7 +156,7 @@ private void Configure(TypeMap typeMap, IConfigurationProvider configurationProv
}

ApplyBaseMaps(typeMap, typeMap, configurationProvider);
ApplyDerivedMaps(typeMap, typeMap, configurationProvider);
ApplyDerivedMaps(typeMap, typeMap, configurationProvider);
ApplyMemberMaps(typeMap, configurationProvider);
}

Expand Down Expand Up @@ -209,9 +209,9 @@ private void ApplyBaseMaps(TypeMap derivedMap, TypeMap currentMap, IConfiguratio
}

private void ApplyMemberMaps(TypeMap mainMap, IConfigurationProvider configurationProvider)
{
AddMemberMaps(mainMap.IncludedMembers, mainMap, configurationProvider);
AddMemberMaps(mainMap.GetUntypedIncludedMembers(), mainMap, configurationProvider);
{
AddMemberMaps(mainMap.IncludedMembers, mainMap, configurationProvider);
AddMemberMaps(mainMap.GetUntypedIncludedMembers(), mainMap, configurationProvider);
}

private void AddMemberMaps(LambdaExpression[] includedMembers, TypeMap mainMap, IConfigurationProvider configurationProvider)
Expand All @@ -226,15 +226,29 @@ private void ApplyDerivedMaps(TypeMap baseMap, TypeMap typeMap, IConfigurationPr
{
foreach (var derivedMap in configurationProvider.GetIncludedTypeMaps(typeMap.IncludedDerivedTypes))
{
derivedMap.IncludeBaseTypes(typeMap.SourceType, typeMap.DestinationType);
derivedMap.IncludeBaseTypes(typeMap.SourceType, typeMap.DestinationType);
derivedMap.AddInheritedMap(baseMap);
ApplyDerivedMaps(baseMap, derivedMap, configurationProvider);
}
}
}

public IList<ICtorParameterConfiguration> GetCtorParameterConfigs(TypeMap typeMap)
{
var list = new List<ICtorParameterConfiguration>();
foreach(var config in _typeMapConfigs.Where(c => c.SourceType == typeMap.SourceType && c.DestinationType == typeMap.DestinationType))
{
if (!(config is MappingExpressionBase mappingExpression))
continue;

list.AddRange(mappingExpression.GetCtorParameterConfigs());
}
return list;
}

}

public readonly struct IncludedMember
{
{
public IncludedMember(TypeMap typeMap, LambdaExpression memberExpression)
{
TypeMap = typeMap;
Expand Down
4 changes: 2 additions & 2 deletions src/AutoMapper/TypeMapFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ private bool MapDestinationCtorToSource(TypeMap typeMap, ConstructorInfo destCto
if (ctorParameters.Length == 0 || !options.ConstructorMappingEnabled)
return false;

var ctorMap = new ConstructorMap(destCtor, typeMap);
var ctorMap = new ConstructorMap(destCtor, typeMap, options);

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)
if((!canResolve && parameter.IsOptional) || ctorMap.ContainsCtorParameterConfig(parameter.Name))
{
canResolve = true;
}
Expand Down
129 changes: 129 additions & 0 deletions src/UnitTests/Constructors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1609,4 +1609,133 @@ 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();
}
}

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();
}
}

}