Skip to content

Commit 020ee85

Browse files
committed
Support for configuring a condition to have a particular target type map to null
1 parent 0516392 commit 020ee85

File tree

8 files changed

+147
-20
lines changed

8 files changed

+147
-20
lines changed

AgileMapper.Net40/AgileMapper.Net40.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
<Compile Include="..\VersionInfo.cs">
5353
<Link>Properties\VersionInfo.cs</Link>
5454
</Compile>
55+
<Compile Include="Configuration\MapToNullCondition.cs" />
5556
<Compile Include="Properties\AssemblyInfo.Net40.cs" />
5657
</ItemGroup>
5758
<ItemGroup>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace AgileObjects.AgileMapper.Configuration
2+
{
3+
internal class MapToNullCondition : UserConfiguredItemBase
4+
{
5+
public MapToNullCondition(MappingConfigInfo configInfo)
6+
: base(configInfo)
7+
{
8+
}
9+
}
10+
}

AgileMapper.UnitTests/AgileMapper.UnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
<Link>VersionInfo.cs</Link>
7676
</Compile>
7777
<Compile Include="Configuration\WhenConfiguringDerivedTypesIncorrectly.cs" />
78+
<Compile Include="Configuration\WhenMappingToNull.cs" />
7879
<Compile Include="Dictionaries\Configuration\WhenConfiguringSourceDictionaryMapping.cs" />
7980
<Compile Include="Dictionaries\Configuration\WhenConfiguringDictionaryMappingIncorrectly.cs" />
8081
<Compile Include="Dictionaries\Configuration\WhenConfiguringNestedDictionaryMapping.cs" />
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace AgileObjects.AgileMapper.UnitTests.Configuration
2+
{
3+
using Shouldly;
4+
using TestClasses;
5+
using Xunit;
6+
7+
public class WhenMappingToNull
8+
{
9+
[Fact]
10+
public void ShouldApplyAUserConfiguredCondition()
11+
{
12+
using (var mapper = Mapper.CreateNew())
13+
{
14+
mapper.WhenMapping
15+
.To<Address>()
16+
.If((o, a) => string.IsNullOrWhiteSpace(a.Line1))
17+
.MapToNull();
18+
19+
var source = new CustomerViewModel { Name = "Bob" };
20+
var result = mapper.Map(source).ToANew<Customer>();
21+
22+
result.Address.ShouldBeNull();
23+
}
24+
}
25+
}
26+
}

AgileMapper/Api/Configuration/IConditionalRootMappingConfigurator.cs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,25 @@ namespace AgileObjects.AgileMapper.Api.Configuration
88
public interface IConditionalRootMappingConfigurator<TSource, TTarget>
99
: IRootMappingConfigurator<TSource, TTarget>
1010
{
11-
/// <summary>
12-
/// Map the source type being configured to the derived target type specified by the type argument if
13-
/// the preceding condition evaluates to true.
14-
/// </summary>
15-
/// <typeparam name="TDerivedTarget">The derived target type to create.</typeparam>
16-
/// <returns>
17-
/// A MappingConfigContinuation to enable further configuration of mappings from and to the source and
18-
/// target type being configured.
11+
/// <summary>
12+
/// Map the source type being configured to the derived target type specified by the type argument if
13+
/// the preceding condition evaluates to true.
14+
/// </summary>
15+
/// <typeparam name="TDerivedTarget">The derived target type to create.</typeparam>
16+
/// <returns>
17+
/// A MappingConfigContinuation to enable further configuration of mappings from and to the source and
18+
/// target type being configured.
1919
/// </returns>
2020
MappingConfigContinuation<TSource, TTarget> MapTo<TDerivedTarget>()
2121
where TDerivedTarget : TTarget;
22+
23+
/// <summary>
24+
/// Map the target type being configured to null if the preceding condition evaluates to true.
25+
/// </summary>
26+
/// <returns>
27+
/// A MappingConfigContinuation to enable further configuration of mappings from and to the source and
28+
/// target type being configured.
29+
/// </returns>
30+
MappingConfigContinuation<TSource, TTarget> MapToNull();
2231
}
2332
}

AgileMapper/Api/Configuration/MappingConfigurator.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,15 @@ public MappingConfigContinuation<TSource, TTarget> MapTo<TDerivedTarget>()
170170
return derivedTypePair.To<TDerivedTarget>();
171171
}
172172

173+
public MappingConfigContinuation<TSource, TTarget> MapToNull()
174+
{
175+
var condition = new MapToNullCondition(ConfigInfo.ForTargetType<TTarget>());
176+
177+
ConfigInfo.MapperContext.UserConfigurations.Add(condition);
178+
179+
return new MappingConfigContinuation<TSource, TTarget>(ConfigInfo);
180+
}
181+
173182
public DerivedPairTargetTypeSpecifier<TSource, TDerivedSource, TTarget> Map<TDerivedSource>() where TDerivedSource : TSource
174183
=> new DerivedPairTargetTypeSpecifier<TSource, TDerivedSource, TTarget>(ConfigInfo);
175184

AgileMapper/Configuration/UserConfigurationSet.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
internal class UserConfigurationSet
1313
{
1414
private readonly ICollection<ObjectTrackingMode> _trackingModeSettings;
15+
private readonly ICollection<MapToNullCondition> _mapToNullConditions;
1516
private readonly ICollection<NullCollectionsSetting> _nullCollectionSettings;
1617
private readonly ICollection<ConfiguredObjectFactory> _objectFactories;
1718
private readonly ICollection<ConfiguredIgnoredMember> _ignoredMembers;
@@ -24,6 +25,7 @@ internal class UserConfigurationSet
2425
public UserConfigurationSet(MapperContext mapperContext)
2526
{
2627
_trackingModeSettings = new List<ObjectTrackingMode>();
28+
_mapToNullConditions = new List<MapToNullCondition>();
2729
_nullCollectionSettings = new List<NullCollectionsSetting>();
2830
_objectFactories = new List<ConfiguredObjectFactory>();
2931
Identifiers = new MemberIdentifierSet();
@@ -54,6 +56,18 @@ public bool DisableObjectTracking(IBasicMapperData basicData)
5456

5557
#endregion
5658

59+
#region MapToNullConditions
60+
61+
public void Add(MapToNullCondition condition)
62+
{
63+
_mapToNullConditions.Add(condition);
64+
}
65+
66+
public Expression GetMapToNullConditionOrNull(IMemberMapperData mapperData)
67+
=> _mapToNullConditions.FindMatch(mapperData)?.GetConditionOrNull(mapperData);
68+
69+
#endregion
70+
5771
#region Null Collections
5872

5973
public void Add(NullCollectionsSetting setting) => _nullCollectionSettings.Add(setting);

AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,14 @@ public Expression Create(IObjectMappingData mappingData)
4141
: derivedTypeMappings;
4242
}
4343

44-
var basicMapperData = mapperData.WithNoTargetMember();
45-
var preMappingCallback = GetMappingCallbackOrNull(CallbackPosition.Before, basicMapperData, mapperData);
46-
var postMappingCallback = GetMappingCallbackOrNull(CallbackPosition.After, basicMapperData, mapperData);
44+
var mappingDecor = GetMappingExtras(mapperData);
4745

4846
mappingExpressions.AddUnlessNullOrEmpty(derivedTypeMappings);
49-
mappingExpressions.AddUnlessNullOrEmpty(preMappingCallback);
47+
mappingExpressions.AddUnlessNullOrEmpty(mappingDecor.PreMappingCallback);
5048
mappingExpressions.AddRange(GetObjectPopulation(mappingData));
51-
mappingExpressions.AddUnlessNullOrEmpty(postMappingCallback);
49+
mappingExpressions.AddUnlessNullOrEmpty(mappingDecor.PostMappingCallback);
5250

53-
var mappingBlock = GetMappingBlock(mappingExpressions, mapperData);
51+
var mappingBlock = GetMappingBlock(mappingExpressions, mappingDecor);
5452
var mappingBlockWithTryCatch = WrapInTryCatch(mappingBlock, mapperData);
5553

5654
return mappingBlockWithTryCatch;
@@ -76,6 +74,20 @@ private bool MappingAlwaysBranchesToDerivedType(IObjectMappingData mappingData,
7674

7775
protected abstract Expression GetDerivedTypeMappings(IObjectMappingData mappingData);
7876

77+
private static MappingExtras GetMappingExtras(ObjectMapperData mapperData)
78+
{
79+
var basicMapperData = mapperData.WithNoTargetMember();
80+
var preMappingCallback = GetMappingCallbackOrNull(CallbackPosition.Before, basicMapperData, mapperData);
81+
var postMappingCallback = GetMappingCallbackOrNull(CallbackPosition.After, basicMapperData, mapperData);
82+
var mapToNullCondition = GetMapToNullConditionOrNull(mapperData);
83+
84+
return new MappingExtras(
85+
mapperData,
86+
preMappingCallback,
87+
postMappingCallback,
88+
mapToNullCondition);
89+
}
90+
7991
protected static Expression GetMappingCallbackOrNull(
8092
CallbackPosition callbackPosition,
8193
IBasicMapperData basicData,
@@ -84,43 +96,61 @@ protected static Expression GetMappingCallbackOrNull(
8496
return mapperData.MapperContext.UserConfigurations.GetCallbackOrNull(callbackPosition, basicData, mapperData);
8597
}
8698

99+
private static Expression GetMapToNullConditionOrNull(IMemberMapperData mapperData)
100+
=> mapperData.MapperContext.UserConfigurations.GetMapToNullConditionOrNull(mapperData);
101+
87102
protected abstract IEnumerable<Expression> GetObjectPopulation(IObjectMappingData mappingData);
88103

89-
private Expression GetMappingBlock(IList<Expression> mappingExpressions, ObjectMapperData mapperData)
104+
private Expression GetMappingBlock(IList<Expression> mappingExpressions, MappingExtras mappingExtras)
90105
{
106+
Expression returnExpression;
107+
108+
var mapperData = mappingExtras.MapperData;
109+
91110
if (mappingExpressions[0].NodeType != ExpressionType.Block)
92111
{
93112
if (mappingExpressions[0].NodeType == ExpressionType.MemberAccess)
94113
{
95-
return mappingExpressions[0];
114+
return GetReturnExpression(mappingExpressions[0], mappingExtras);
96115
}
97116

98117
var objectAssignment = mappingExpressions.First(exp => exp.NodeType == ExpressionType.Assign);
99118

100119
if (mappingExpressions.Last() == objectAssignment)
101120
{
102121
var assignedValue = ((BinaryExpression)objectAssignment).Right;
122+
returnExpression = GetReturnExpression(assignedValue, mappingExtras);
103123

104124
if (mappingExpressions.Count == 1)
105125
{
106-
return assignedValue;
126+
return returnExpression;
107127
}
108128

109-
mappingExpressions[mappingExpressions.Count - 1] = mapperData.GetReturnLabel(assignedValue);
129+
mappingExpressions[mappingExpressions.Count - 1] = mapperData.GetReturnLabel(returnExpression);
110130

111131
return Expression.Block(mappingExpressions);
112132
}
113133
}
114134

115-
var returnValue = GetReturnValue(mapperData);
135+
returnExpression = GetReturnExpression(GetReturnValue(mapperData), mappingExtras);
116136

117-
mappingExpressions.Add(mapperData.GetReturnLabel(returnValue));
137+
mappingExpressions.Add(mapperData.GetReturnLabel(returnExpression));
118138

119139
var mappingBlock = Expression.Block(new[] { mapperData.InstanceVariable }, mappingExpressions);
120140

121141
return mappingBlock;
122142
}
123143

144+
private static Expression GetReturnExpression(Expression returnValue, MappingExtras mappingExtras)
145+
{
146+
return (mappingExtras.MapToNullCondition != null)
147+
? Expression.Condition(
148+
mappingExtras.MapToNullCondition,
149+
returnValue.Type.ToDefaultExpression(),
150+
returnValue)
151+
: returnValue;
152+
}
153+
124154
protected abstract Expression GetReturnValue(ObjectMapperData mapperData);
125155

126156
private static Expression WrapInTryCatch(Expression mappingBlock, IMemberMapperData mapperData)
@@ -182,5 +212,32 @@ private static Expression WrapInTryCatch(Expression mappingBlock, IMemberMapperD
182212
public virtual void Reset()
183213
{
184214
}
215+
216+
#region Helper Class
217+
218+
internal class MappingExtras
219+
{
220+
public MappingExtras(
221+
ObjectMapperData mapperData,
222+
Expression preMappingCallback,
223+
Expression postMappingCallback,
224+
Expression mapToNullCondition)
225+
{
226+
MapperData = mapperData;
227+
PreMappingCallback = preMappingCallback;
228+
PostMappingCallback = postMappingCallback;
229+
MapToNullCondition = mapToNullCondition;
230+
}
231+
232+
public ObjectMapperData MapperData { get; }
233+
234+
public Expression PreMappingCallback { get; }
235+
236+
public Expression PostMappingCallback { get; }
237+
238+
public Expression MapToNullCondition { get; }
239+
}
240+
241+
#endregion
185242
}
186243
}

0 commit comments

Comments
 (0)