Skip to content

Instantly share code, notes, and snippets.

@ilyapalkin
Last active January 25, 2024 17:13
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ilyapalkin/8822638 to your computer and use it in GitHub Desktop.
Save ilyapalkin/8822638 to your computer and use it in GitHub Desktop.
An abstraction over AutoMapper to map several sources into single destination.
/// <summary>
/// Type mapping api
/// </summary>
public interface IMapper
{
/// <summary>
/// Maps the specified source type instance to destination type instance.
/// </summary>
/// <typeparam name="TSource">Source type.</typeparam>
/// <typeparam name="TDestination">Destination type.</typeparam>
/// <param name="source">The source.</param>
/// <returns>
/// Instance of destination type.
/// </returns>
TDestination Map<TSource, TDestination>(TSource source);
/// <summary>
/// Maps the specified source type instance to destination type instance.
/// </summary>
/// <typeparam name="TDestination">The type of the destination instance.</typeparam>
/// <param name="source">The source instance.</param>
/// <returns>
/// Instance of destination type.
/// </returns>
TDestination MapTo<TDestination>(object source);
/// <summary>
/// Maps the specified source type instance to destination type instance.
/// </summary>
/// <param name="source">The source instance.</param>
/// <returns>Fluent interface for mapping.</returns>
IMapBuilder Map(object source);
}
/// <summary>
/// Fluent interface for mapping.
/// </summary>
public interface IMapBuilder
{
/// <summary>
/// Maps the specified source type instance to destination type instance.
/// </summary>
/// <param name="source">The source instance.</param>
/// <returns>Fluent interface for mapping.</returns>
IMapBuilder Map(object source);
/// <summary>
/// Maps the specified earlier source type instances to destination type instance.
/// </summary>
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <param name="destination">The destination object.</param>
/// <returns>
/// Instance of destination type.
/// </returns>
TDestination To<TDestination>(TDestination destination);
/// <summary>
/// Maps the specified earlier source type instances to destination type instance.
/// </summary>
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <returns>
/// Instance of destination type.
/// </returns>
TDestination To<TDestination>();
}
/// <summary>
/// Type mapping api
/// </summary>
public class Mapper : IMapper
{
/// <summary>
/// Maps the specified source type instance to destination type instance.
/// </summary>
/// <typeparam name="TSource">Source type.</typeparam>
/// <typeparam name="TDestination">Destination type.</typeparam>
/// <param name="source">The source.</param>
/// <returns>
/// Instance of destination type.
/// </returns>
public TDestination Map<TSource, TDestination>(TSource source)
{
return MapTo<TDestination>(source);
}
/// <summary>
/// Maps the specified source type instance to destination type instance.
/// </summary>
/// <typeparam name="TDestination">The type of the destination instance.</typeparam>
/// <param name="source">The source instance.</param>
/// <returns>
/// Instance of destination type.
/// </returns>
public TDestination MapTo<TDestination>(object source)
{
return Map(source)
.To<TDestination>();
}
/// <summary>
/// Maps the specified source type instance to destination type instance.
/// </summary>
/// <param name="source">The source instance.</param>
/// <returns>Fluent interface for mapping.</returns>
public IMapBuilder Map(object source)
{
return new MapBuilder(source);
}
#region Internal types
/// <summary>
/// Fluent interface for mapping.
/// </summary>
internal class MapBuilder : IMapBuilder
{
private readonly List<object> sources = new List<object>();
/// <summary>
/// Initialises a new instance of the <see cref="MapBuilder"/> class.
/// </summary>
/// <param name="source">The source instance.</param>
public MapBuilder(object source)
{
sources.Add(source);
}
/// <summary>
/// Maps the specified source type instance to destination type instance.
/// </summary>
/// <param name="source">The source instance.</param>
/// <returns>Fluent interface for mapping.</returns>
public IMapBuilder Map(object source)
{
sources.Add(source);
return this;
}
/// <summary>
/// Maps the specified earlier source type instances to destination type instance.
/// </summary>
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <param name="destination">The destination object.</param>
/// <returns>
/// Instance of destination type.
/// </returns>
public TDestination To<TDestination>(TDestination destination)
{
sources.ForEach(source => Map(source, destination));
return destination;
}
/// <summary>
/// Maps the specified earlier source type instances to destination type instance.
/// </summary>
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <returns>
/// Instance of destination type.
/// </returns>
public TDestination To<TDestination>()
{
return sources.Aggregate(default(TDestination), (destination, source) => Map(source, destination));
}
/// <summary>
/// Maps specified earlier source type instances to destination type instance.
/// </summary>
/// <param name="destinationType">The type of the destination.</param>
/// <returns>
/// Instance of destination type.
/// </returns>
public object ToType(Type destinationType)
{
return sources.Aggregate<object, object>(null, (destination, source) => Map(source, destination, destinationType));
}
private TDestination Map<TDestination>(object source, TDestination destination)
{
return destination != null
? (TDestination)AutoMapper.Mapper.Map(source, destination, source.GetType(), typeof(TDestination))
: AutoMapper.Mapper.Map<TDestination>(source);
}
private object Map(object source, object destination, Type destinationType)
{
return destination != null
? AutoMapper.Mapper.Map(source, destination, source.GetType(), destinationType)
: AutoMapper.Mapper.Map(source, source.GetType(), destinationType);
}
}
#endregion
}
public class MapperTest
{
private readonly IMapper target;
public MapperTest()
{
AutoMapper.Mapper.CreateMap<SourceA, Destination>();
AutoMapper.Mapper.CreateMap<SourceB, Destination>();
target = new Mapper();
}
[Fact]
public void Map_MapsSourceToDestinationObject_viaMapBuilder()
{
// Arrange.
var source = new SourceA { A = "a value" };
Destination destination = new DestinationProxy();
// Act.
var result = target.Map(source).To(destination);
// Assert.
result.Should().BeSameAs(destination);
Assert.Equal(source.A, result.A);
result.A.Should().Be(source.A);
result.B.Should().BeNull();
}
[Fact]
public void Map_MapsSourceToDestinationType_viaMapBuilder()
{
// Arrange.
var source = new SourceA { A = "a value" };
// Act.
var result = target.Map(source).To<Destination>();
// Assert.
result.A.Should().Be(source.A);
result.B.Should().BeNull();
}
[Fact]
public void Map_MapsManySourcesToDestinationObject_viaMapBuilder()
{
// Arrange.
var sourceA = new SourceA { A = "a value" };
var sourceB = new SourceB { B = "b value" };
Destination destination = new DestinationProxy();
// Act.
var result = target.Map(sourceA).Map(sourceB).To(destination);
// Assert.
result.Should().BeSameAs(destination);
result.A.Should().Be(sourceA.A);
result.B.Should().Be(sourceB.B);
}
[Fact]
public void Map_MapsManySourcesToDestinationType_viaMapBuilder()
{
// Arrange.
var sourceA = new SourceA { A = "a value" };
var sourceB = new SourceB { B = "b value" };
// Act.
var result = target.Map(sourceA).Map(sourceB).To<Destination>();
// Assert.
result.A.Should().Be(sourceA.A);
result.B.Should().Be(sourceB.B);
}
[Fact]
public void Map_MapsManySourcesToTypeOfDestination_viaMapBuilder()
{
// Arrange.
var sourceA = new SourceA { A = "a value" };
var sourceB = new SourceB { B = "b value" };
// Act.
var result = target.Map(sourceA).Map(sourceB).ToType(typeof(Destination));
// Assert.
result.Should().BeOfType<Destination>();
var destination = (Destination)result;
destination.A.Should().Be(sourceA.A);
destination.B.Should().Be(sourceB.B);
}
[Fact]
public void Map_MapsSourceToDestinationType()
{
// Arrange.
var source = new SourceA { A = "a value" };
// Act.
var result = target.Map<SourceA, Destination>(source);
// Assert.
result.A.Should().Be(source.A);
result.B.Should().BeNull();
}
[Fact]
public void MapTo_MapsSourceToDestinationType()
{
// Arrange.
var source = new SourceA { A = "a value" };
// Act.
var result = target.MapTo<Destination>(source);
// Assert.
result.A.Should().Be(source.A);
result.B.Should().BeNull();
}
#region Internals
private class SourceA
{
public string A { get; set; }
}
private class SourceB
{
public string B { get; set; }
}
private class Destination
{
public string A { get; private set; }
public string B { get; private set; }
}
/// <summary>
/// It is used to make sure that mapper works with proxies.
/// </summary>
private class DestinationProxy : Destination
{
}
#endregion
}
@crispyscripts
Copy link

Needs to come with AutoMapper as standard in my opinion, good work!

@akramg9
Copy link

akramg9 commented Mar 24, 2020

Getting following error after using classes as specified:
An object reference is required for the non-static field, method, or property 'Mapper.Map(object, object, Type, Type)'
I am looking a generic way to map single source from multiple destination.

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment