Skip to content

Commit

Permalink
Merge pull request MapsterMapper#54 from chaowlert/after-mapping
Browse files Browse the repository at this point in the history
After mapping
  • Loading branch information
eswann committed Mar 7, 2016
2 parents 2325f46 + dc81925 commit 87db1f6
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 38 deletions.
1 change: 1 addition & 0 deletions src/Mapster.Tests/Mapster.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
<Compile Include="WhenMappingRecordTypes.cs" />
<Compile Include="WhenMappingIgnoreNullValues.cs" />
<Compile Include="WhenMappingWithExtensionMethods.cs" />
<Compile Include="WhenPerformingAfterMapping.cs" />
<Compile Include="WhenPreserveReferences.cs" />
<Compile Include="WhenAddingCustomMappings.cs" />
<Compile Include="WhenHandlingUnmappedMembers.cs" />
Expand Down
73 changes: 73 additions & 0 deletions src/Mapster.Tests/WhenPerformingAfterMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;
using NUnit.Framework;
using Should;

namespace Mapster.Tests
{
[TestFixture]
public class WhenPerformingAfterMapping
{
[TearDown]
public void TearDown()
{
TypeAdapterConfig.GlobalSettings.Clear();
}

[Test]
public void After_Mapping()
{
TypeAdapterConfig<SimplePoco, SimpleDto>.NewConfig()
.AfterMapping((src, dest) => dest.Name += "xxx");

var poco = new SimplePoco
{
Id = Guid.NewGuid(),
Name = "test",
};
var result = TypeAdapter.Adapt<SimpleDto>(poco);

result.Id.ShouldEqual(poco.Id);
result.Name.ShouldEqual(poco.Name + "xxx");
}

[Test]
public void After_Mapping_With_DestinationType_Setting()
{
TypeAdapterConfig.GlobalSettings.ForDestinationType<IValidatable>()
.AfterMapping(dest => dest.Validate());

var poco = new SimplePoco
{
Id = Guid.NewGuid(),
Name = "test",
};
var result = TypeAdapter.Adapt<SimpleDto>(poco);

result.IsValidated.ShouldBeTrue();
}

public interface IValidatable
{
void Validate();
}

public class SimplePoco
{
public Guid Id { get; set; }
public string Name { get; set; }
}

public class SimpleDto : IValidatable
{
public Guid Id { get; set; }
public string Name { get; set; }

public bool IsValidated { get; private set; }

public void Validate()
{
this.IsValidated = true;
}
}
}
}
42 changes: 37 additions & 5 deletions src/Mapster/Adapters/BaseAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ protected virtual bool CanInline(Expression source, Expression destination, Comp
{
if (arg.MapType == MapType.MapToTarget)
return false;
if (arg.Settings.ConstructUsing != null &&
arg.Settings.ConstructUsing.Body.NodeType != ExpressionType.New &&
arg.Settings.ConstructUsing.Body.NodeType != ExpressionType.MemberInit)
var constructUsing = arg.Settings.ConstructUsingFactory?.Invoke(arg);
if (constructUsing != null &&
constructUsing.Body.NodeType != ExpressionType.New &&
constructUsing.Body.NodeType != ExpressionType.MemberInit)
{
if (arg.MapType == MapType.Projection)
throw new InvalidOperationException(
Expand All @@ -68,6 +69,9 @@ protected virtual bool CanInline(Expression source, Expression destination, Comp
!arg.SourceType.GetTypeInfo().IsValueType &&
!arg.DestinationType.GetTypeInfo().IsValueType)
return false;
if (arg.Settings.AfterMappingFactories.Count > 0 &&
arg.MapType != MapType.Projection)
return false;
return true;
}

Expand All @@ -89,6 +93,33 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression des

var set = CreateBlockExpression(source, result, arg);

if (arg.Settings.AfterMappingFactories.Count > 0)
{
//var result = adapt(source);
//action(source, result);

var actions = new List<Expression> {set};

foreach (var afterMappingFactory in arg.Settings.AfterMappingFactories)
{
var afterMapping = afterMappingFactory(arg);
var args = afterMapping.Parameters;
Expression invoke;
if (args[0].Type == source.Type && args[1].Type == result.Type)
{
var replacer = new ParameterExpressionReplacer(args, source, result);
invoke = replacer.Visit(afterMapping.Body);
}
else
{
invoke = Expression.Invoke(afterMapping, source.To(args[0].Type), result.To(args[1].Type));
}
actions.Add(invoke);
}

set = Expression.Block(actions);
}

if (arg.Settings.PreserveReference == true &&
!arg.SourceType.GetTypeInfo().IsValueType &&
!arg.DestinationType.GetTypeInfo().IsValueType)
Expand Down Expand Up @@ -177,8 +208,9 @@ protected virtual Expression CreateInstantiationExpression(Expression source, Co
{
//new TDestination()

return arg.Settings.ConstructUsing != null
? arg.Settings.ConstructUsing.Apply(source).TrimConversion().To(arg.DestinationType)
var constructUsing = arg.Settings.ConstructUsingFactory?.Invoke(arg);
return constructUsing != null
? constructUsing.Apply(source).TrimConversion().To(arg.DestinationType)
: Expression.New(arg.DestinationType);
}

Expand Down
42 changes: 42 additions & 0 deletions src/Mapster/Adapters/BaseAfterMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Linq.Expressions;

namespace Mapster.Adapters
{
public abstract class BaseAfterMapper
{
protected virtual int Score => 0;

public virtual int? Priority(Type sourceType, Type destinationType, MapType mapType)
{
return CanMap(sourceType, destinationType, mapType) ? this.Score : (int?)null;
}

protected abstract bool CanMap(Type sourceType, Type destinationType, MapType mapType);

public LambdaExpression CreateAfterMapFunc(CompileArgument arg)
{
var p = Expression.Parameter(arg.SourceType);
var p2 = Expression.Parameter(arg.DestinationType);
var body = CreateExpressionBody(p, p2, arg);
return Expression.Lambda(body, p, p2);
}

protected abstract Expression CreateExpressionBody(Expression source, Expression destination, CompileArgument arg);

public TypeAdapterRule CreateRule()
{
var settings = new TypeAdapterSettings();
settings.AfterMappingFactories.Add(this.CreateAfterMapFunc);
var rule = new TypeAdapterRule
{
Priority = this.Priority,
Settings = settings,
};
DecorateRule(rule);
return rule;
}

protected virtual void DecorateRule(TypeAdapterRule rule) { }
}
}
2 changes: 1 addition & 1 deletion src/Mapster/Adapters/RecordTypeAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ protected override Expression CreateInstantiationExpression(Expression source, C
{
//new TDestination(src.Prop1, src.Prop2)

if (arg.Settings.ConstructUsing != null)
if (arg.Settings.ConstructUsingFactory != null)
return base.CreateInstantiationExpression(source, arg);

var classConverter = CreateClassConverter(source, null, arg);
Expand Down
1 change: 1 addition & 0 deletions src/Mapster/Mapster.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<ItemGroup>
<Compile Include="Adapter.cs" />
<Compile Include="Adapters\BaseAdapter.cs" />
<Compile Include="Adapters\BaseAfterMapper.cs" />
<Compile Include="Adapters\BaseClassAdapter.cs" />
<Compile Include="Adapters\RecordTypeAdapter.cs" />
<Compile Include="IRegister.cs" />
Expand Down
60 changes: 43 additions & 17 deletions src/Mapster/TypeAdapterConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ public TypeAdapterSetter When(Func<Type, Type, MapType, bool> canMap)
return new TypeAdapterSetter<TSource, TDestination>(settings, this);
}

public TypeAdapterSetter<TDestination> ForDestinationType<TDestination>()
{
var key = new TypeTuple(typeof(void), typeof(TDestination));
var settings = GetSettings(key);
return new TypeAdapterSetter<TDestination>(settings, this);
}

private TypeAdapterSettings GetSettings(TypeTuple key)
{
TypeAdapterRule rule;
Expand All @@ -83,23 +90,9 @@ private TypeAdapterSettings GetSettings(TypeTuple key)
{
if (!this.RuleMap.TryGetValue(key, out rule))
{
rule = new TypeAdapterRule
{
Priority = (sourceType, destinationType, mapType) =>
{
var score1 = GetSubclassDistance(destinationType, key.Destination, this.AllowImplicitDestinationInheritance);
if (score1 == null)
return null;
var score2 = GetSubclassDistance(sourceType, key.Source, true);
if (score2 == null)
return null;
return score1.Value + score2.Value;
},
Settings = new TypeAdapterSettings
{
DestinationType = key.Destination,
},
};
rule = key.Source == typeof (void)
? CreateDestinationTypeRule(key)
: CreateTypeTupleRule(key);
this.Rules.Add(rule);
this.RuleMap.Add(key, rule);
}
Expand All @@ -108,6 +101,39 @@ private TypeAdapterSettings GetSettings(TypeTuple key)
return rule.Settings;
}

private TypeAdapterRule CreateTypeTupleRule(TypeTuple key)
{
return new TypeAdapterRule
{
Priority = (sourceType, destinationType, mapType) =>
{
var score1 = GetSubclassDistance(destinationType, key.Destination, this.AllowImplicitDestinationInheritance);
if (score1 == null)
return null;
var score2 = GetSubclassDistance(sourceType, key.Source, true);
if (score2 == null)
return null;
return score1.Value + score2.Value;
},
Settings = new TypeAdapterSettings
{
DestinationType = key.Destination,
},
};
}

private static TypeAdapterRule CreateDestinationTypeRule(TypeTuple key)
{
return new TypeAdapterRule
{
Priority = (sourceType, destinationType, mapType) => GetSubclassDistance(destinationType, key.Destination, true),
Settings = new TypeAdapterSettings
{
DestinationType = key.Destination,
},
};
}

private static int? GetSubclassDistance(Type type1, Type type2, bool allowInheritance)
{
if (type1 == type2)
Expand Down
Loading

0 comments on commit 87db1f6

Please sign in to comment.