Skip to content

Commit be54421

Browse files
committed
consider member value resolvers and value converters for source validation
1 parent 9498221 commit be54421

File tree

4 files changed

+53
-19
lines changed

4 files changed

+53
-19
lines changed

src/AutoMapper/MemberMap.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ public class MemberMap
3939
public virtual ValueResolverConfiguration ValueResolverConfig { get => default; set { } }
4040
public virtual ValueResolverConfiguration ValueConverterConfig { get => default; set { } }
4141
public virtual IReadOnlyCollection<ValueTransformerConfiguration> ValueTransformers => Array.Empty<ValueTransformerConfiguration>();
42-
public MemberInfo SourceMember => CustomMapExpression.GetMember() ?? SourceMembers.LastOrDefault();
42+
public MemberInfo SourceMember => CustomMapExpression.GetMember() ?? SourceMembers.FirstOrDefault();
43+
public string GetSourceMemberName() =>
44+
ValueConverterConfig?.GetSourceMemberName() ?? ValueResolverConfig?.GetSourceMemberName() ?? SourceMember?.Name;
4345
public bool MustUseDestination => UseDestinationValue is true || !CanBeSet;
4446
public void MapFrom(LambdaExpression sourceMember)
4547
{
@@ -92,6 +94,7 @@ public ValueResolverConfiguration(object instance, Type interfaceType)
9294
InterfaceType = interfaceType;
9395
}
9496
public Type ResolvedType => InterfaceType.GenericTypeArguments.Last();
97+
public string GetSourceMemberName() => SourceMember == null ? SourceMemberName : SourceMember.GetMember()?.Name;
9598
}
9699
public readonly struct ValueTransformerConfiguration
97100
{

src/AutoMapper/TypeMap.cs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -176,41 +176,28 @@ private void AddPropertyMap(PropertyMap propertyMap)
176176
}
177177
public string[] GetUnmappedPropertyNames()
178178
{
179-
var autoMappedProperties = GetPropertyNames(PropertyMaps);
180179
IEnumerable<string> properties;
181180
if (ConfiguredMemberList == MemberList.Destination)
182181
{
183182
properties = Profile.CreateTypeDetails(DestinationType).WriteAccessors
184183
.Select(p => p.Name)
185184
.Where(p => !ConstructorParameterMatches(p))
186-
.Except(autoMappedProperties)
185+
.Except(MappedMembers().Select(m => m.DestinationName))
187186
.Except(PathMaps.Select(p => p.MemberPath.First.Name));
188187
}
189188
else
190189
{
191-
var redirectedSourceMembers = MemberMaps
192-
.Where(pm => pm.IsMapped && pm.SourceMember != null && pm.SourceMember.Name != pm.DestinationName)
193-
.Select(pm => pm.SourceMember.Name);
194-
195190
var ignoredSourceMembers = _sourceMemberConfigs?.Values
196191
.Where(smc => smc.IsIgnored())
197192
.Select(pm => pm.SourceMember.Name);
198-
199193
properties = Profile.CreateTypeDetails(SourceType).ReadAccessors
200194
.Select(p => p.Name)
201-
.Except(autoMappedProperties)
202-
.Except(redirectedSourceMembers)
195+
.Except(MappedMembers().Select(m => m.GetSourceMemberName()))
203196
.Except(ignoredSourceMembers ?? Array.Empty<string>());
204197
}
205198
return properties.Where(memberName => !Profile.GlobalIgnores.Any(memberName.StartsWith)).ToArray();
206-
string GetPropertyName(PropertyMap pm) => ConfiguredMemberList == MemberList.Destination
207-
? pm.DestinationName
208-
: pm.SourceMembers.Length > 1
209-
? pm.SourceMembers[0].Name
210-
: pm.SourceMember?.Name ?? pm.DestinationName;
211-
string[] GetPropertyNames(IEnumerable<PropertyMap> propertyMaps) => propertyMaps.Where(pm => pm.IsMapped).Select(GetPropertyName).ToArray();
199+
IEnumerable<MemberMap> MappedMembers() => MemberMaps.Where(pm => pm.IsMapped);
212200
}
213-
214201
public PropertyMap FindOrCreatePropertyMapFor(MemberInfo destinationProperty, Type destinationPropertyType)
215202
{
216203
var propertyMap = GetPropertyMap(destinationProperty.Name);

src/UnitTests/BasicFlattening.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
using System.Collections.Generic;
33
using Xunit;
44
using Shouldly;
5-
5+
using System.Linq;
6+
67
namespace AutoMapper.UnitTests
78
{
89
public class BasicFlattening : AutoMapperSpecBase
@@ -232,6 +233,6 @@ public class CustomerDTO
232233
protected override MapperConfiguration CreateConfiguration() => new(cfg => cfg.CreateMap<Customer, CustomerDTO>(MemberList.Source).ForMember(d=>d.Id, o=>o.MapFrom(s=>s.AnotherId)));
233234
[Fact]
234235
public void Should_validate() =>
235-
new Action(AssertConfigurationIsValid).ShouldThrowException<AutoMapperConfigurationException>(ex => ex.Errors[0].UnmappedPropertyNames[0].ShouldBe(nameof(Address.Id)));
236+
new Action(AssertConfigurationIsValid).ShouldThrow<AutoMapperConfigurationException>().Errors.Single().UnmappedPropertyNames.Single().ShouldBe(nameof(Address.Id));
236237
}
237238
}

src/UnitTests/ConfigurationValidation.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,49 @@ public void Should_fail_a_configuration_check()
283283
{
284284
typeof(AutoMapperConfigurationException).ShouldBeThrownBy(AssertConfigurationIsValid);
285285
}
286+
}
287+
288+
public class ResolversWithSourceValidation : AutoMapperSpecBase
289+
{
290+
class Source
291+
{
292+
public int Resolved { get; set; }
293+
public int TypedResolved { get; set; }
294+
public int Converted { get; set; }
295+
public int TypedConverted { get; set; }
296+
}
297+
class Destination
298+
{
299+
public int ResolvedDest { get; set; }
300+
public int TypedResolvedDest { get; set; }
301+
public int ConvertedDest { get; set; }
302+
public int TypedConvertedDest { get; set; }
303+
}
304+
class MemberResolver : IMemberValueResolver<Source, Destination, int, int>
305+
{
306+
public int Resolve(Source source, Destination destination, int sourceMember, int destinationMember, ResolutionContext context) => sourceMember + 5;
307+
}
308+
class ValueConverter : IValueConverter<int, int>
309+
{
310+
public int Convert(int sourceMember, ResolutionContext context) => sourceMember + 5;
311+
}
312+
protected override MapperConfiguration CreateConfiguration() => new(cfg =>
313+
{
314+
cfg.CreateMap<Source, Destination>(MemberList.Source)
315+
.ForMember(d => d.ResolvedDest, o => o.MapFrom<MemberResolver, int>("Resolved"))
316+
.ForMember(d=>d.TypedResolvedDest, o => o.MapFrom<MemberResolver, int>(s => s.TypedResolved))
317+
.ForMember(d => d.ConvertedDest, o => o.ConvertUsing<ValueConverter, int>("Converted"))
318+
.ForMember(d => d.TypedConvertedDest, o => o.ConvertUsing<ValueConverter, int>(s => s.TypedConverted));
319+
});
320+
[Fact]
321+
public void Should_work()
322+
{
323+
var result = Mapper.Map<Source, Destination>(new Source { Resolved = 5, TypedResolved = 5, Converted = 5, TypedConverted = 5});
324+
result.ResolvedDest.ShouldBe(10);
325+
result.TypedResolvedDest.ShouldBe(10);
326+
result.ConvertedDest.ShouldBe(10);
327+
result.TypedConvertedDest.ShouldBe(10);
328+
}
286329
}
287330

288331
public class NonMemberExpressionWithSourceValidation : NonValidatingSpecBase

0 commit comments

Comments
 (0)