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
43 changes: 39 additions & 4 deletions src/AutoMapper/IObjectMapper.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
using System;
using System.Linq.Expressions;

using System.Reflection;

namespace AutoMapper
{
{
using static Expression;

/// <summary>
/// Mapping execution strategy, as a chain of responsibility
/// </summary>
public interface IObjectMapper
public interface IObjectMapper
{
/// <summary>
/// When true, the mapping engine will use this mapper as the strategy
Expand All @@ -26,4 +30,35 @@ public interface IObjectMapper
/// <returns>Map expression</returns>
Expression MapExpression(TypeMapRegistry typeMapRegistry, IConfigurationProvider configurationProvider, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression);
}
}

/// <summary>
/// Base class for simple object mappers that don't want to use expressions.
/// </summary>
/// <typeparam name="TSource">type of the source</typeparam>
/// <typeparam name="TDestination">type of the destination</typeparam>
public abstract class ObjectMapper<TSource, TDestination> : IObjectMapper
{
private static readonly MethodInfo MapMethod = typeof(ObjectMapper<TSource, TDestination>).GetDeclaredMethod("Map");

/// <summary>
/// When true, the mapping engine will use this mapper as the strategy
/// </summary>
/// <param name="context">Resolution context</param>
/// <returns>Is match</returns>
public abstract bool IsMatch(TypePair context);

/// <summary>
/// Performs conversion from source to destination type
/// </summary>
/// <param name="source">Source object</param>
/// <param name="destination">Destination object</param>
/// <param name="context">Resolution context</param>
/// <returns>Destination object</returns>
public abstract TDestination Map(TSource source, TDestination destination, ResolutionContext context);

public Expression MapExpression(TypeMapRegistry typeMapRegistry, IConfigurationProvider configurationProvider, PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression)
{
return Call(Constant(this), MapMethod, sourceExpression, destExpression, contextExpression);
}
}
}
156 changes: 108 additions & 48 deletions src/UnitTests/Mappers/CustomMapperTests.cs
Original file line number Diff line number Diff line change
@@ -1,73 +1,133 @@
namespace AutoMapper.UnitTests.Mappers
using System;
using Should;
using System.Linq;
using System.Linq.Expressions;
using AutoMapper;
using AutoMapper.Mappers;
using Xunit;

namespace AutoMapper.UnitTests.Mappers
{
namespace CustomMapperTests
public class When_adding_a_custom_mapper : NonValidatingSpecBase
{
using System;
using System.Linq.Expressions;
using AutoMapper.Mappers;
using Xunit;

public class When_adding_a_custom_mapper : NonValidatingSpecBase
public When_adding_a_custom_mapper()
{
public When_adding_a_custom_mapper()
{
MapperRegistry.Mappers.Insert(0, new TestObjectMapper());
}
MapperRegistry.Mappers.Insert(0, new TestObjectMapper());
}

protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ClassA, ClassB>()
.ForMember(dest => dest.Destination, opt => opt.MapFrom(src => src.Source));
});
protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ClassA, ClassB>()
.ForMember(dest => dest.Destination, opt => opt.MapFrom(src => src.Source));
});

[Fact]
public void Should_have_valid_configuration()
{
typeof(AutoMapperConfigurationException).ShouldNotBeThrownBy(Configuration.AssertConfigurationIsValid);
}
[Fact]
public void Should_have_valid_configuration()
{
typeof(AutoMapperConfigurationException).ShouldNotBeThrownBy(Configuration.AssertConfigurationIsValid);
}


public class TestObjectMapper : IObjectMapper
public class TestObjectMapper : IObjectMapper
{
public object Map(ResolutionContext context)
{
public object Map(ResolutionContext context)
{
return new DestinationType();
}

public bool IsMatch(TypePair context)
{
return context.SourceType == typeof(SourceType) && context.DestinationType == typeof(DestinationType);
}

public Expression MapExpression(TypeMapRegistry typeMapRegistry, IConfigurationProvider configurationProvider,
PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression)
{
Expression<Func<DestinationType>> expr = () => new DestinationType();

return expr.Body;
}
return new DestinationType();
}

public class ClassA
public bool IsMatch(TypePair context)
{
public SourceType Source { get; set; }
return context.SourceType == typeof(SourceType) && context.DestinationType == typeof(DestinationType);
}

public class ClassB
public Expression MapExpression(TypeMapRegistry typeMapRegistry, IConfigurationProvider configurationProvider,
PropertyMap propertyMap, Expression sourceExpression, Expression destExpression, Expression contextExpression)
{
public DestinationType Destination { get; set; }
Expression<Func<DestinationType>> expr = () => new DestinationType();

return expr.Body;
}
}

public class ClassA
{
public SourceType Source { get; set; }
}

public class ClassB
{
public DestinationType Destination { get; set; }
}

public class SourceType
{
public int Value { get; set; }
}

public class DestinationType
{
public bool Value { get; set; }
}
}

public class When_adding_a_simple_custom_mapper : AutoMapperSpecBase
{
ClassB _destination;

public class SourceType
protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ClassA, ClassB>()
.ForMember(dest => dest.Destination, opt => opt.MapFrom(src => src.Source));
}, MapperRegistry.Mappers.Concat(new[] { new TestObjectMapper() }));

protected override void Because_of()
{
_destination = new ClassB { Destination = new DestinationType() };
Mapper.Map(new ClassA { Source = new SourceType() }, _destination);
}

[Fact]
public void Should_use_the_object_mapper()
{
_destination.Destination.ShouldBeSameAs(TestObjectMapper.Instance);
}

public class TestObjectMapper : ObjectMapper<SourceType, DestinationType>
{
public static DestinationType Instance = new DestinationType();

public override DestinationType Map(SourceType source, DestinationType destination, ResolutionContext context)
{
public int Value { get; set; }
source.ShouldNotBeNull();
destination.ShouldNotBeNull();
context.ShouldNotBeNull();
return Instance;
}

public class DestinationType
public override bool IsMatch(TypePair context)
{
public bool Value { get; set; }
return context.SourceType == typeof(SourceType) && context.DestinationType == typeof(DestinationType);
}
}

public class ClassA
{
public SourceType Source { get; set; }
}

public class ClassB
{
public DestinationType Destination { get; set; }
}

public class SourceType
{
public int Value { get; set; }
}

public class DestinationType
{
public bool Value { get; set; }
}
}
}