Skip to content

Commit

Permalink
Merge pull request #533 from stormaref/master
Browse files Browse the repository at this point in the history
Add IMapFrom interface for automatic mapping configuration
  • Loading branch information
andrerav authored Feb 7, 2023
2 parents 54f075f + f368c63 commit 65b6466
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 0 deletions.
69 changes: 69 additions & 0 deletions src/Mapster.Tests/WhenUsingIMapFrom.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Reflection;
using Mapster.Utils;
using MapsterMapper;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;

namespace Mapster.Tests;

[TestClass]
public class WhenUsingIMapFrom
{
private readonly Mapper _mapper;

public WhenUsingIMapFrom()
{
_mapper = new Mapper();
TypeAdapterConfig.GlobalSettings.ScanInheritedTypes(Assembly.GetExecutingAssembly());
}

[TestMethod]
public void TestIMapFrom_WhenMethodIsNotImplemented()
{
var source = new SourceModel(DesireValues.Text);
var destination = _mapper.Map<DestinationModel>(source);
destination.Type.ShouldBe(DesireValues.Text);
}

[TestMethod]
public void TestIMapFrom_WhenMethodImplemented()
{
var source = new SourceModel(DesireValues.Text);
var destination = _mapper.Map<InheritedDestinationModel>(source);
destination.Type.ShouldBe(DesireValues.Text);
destination.Value.ShouldBe(9);
}
}

internal static class DesireValues
{
internal const string Text = "Test";
internal const int Number = 9;
}

public class SourceModel
{
public SourceModel(string type)
{
Type = type;
}

public string Type { get; set; }
}

public class InheritedDestinationModel : IMapFrom<SourceModel>
{
public string Type { get; set; }
public int Value { get; set; }

public void ConfigureMapping(TypeAdapterConfig config)
{
config.NewConfig<SourceModel, InheritedDestinationModel>()
.Map(dest => dest.Value, _ => DesireValues.Number);
}
}

public class DestinationModel : IMapFrom<SourceModel>
{
public string Type { get; set; }
}
33 changes: 33 additions & 0 deletions src/Mapster.Tests/WhenUsingIMapFromWrong.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Reflection;
using Mapster.Utils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;

namespace Mapster.Tests;

[TestClass]
public class WhenUsingIMapFromWrong
{
[TestMethod]
public void TestIMapFrom_WhenMethodImplementedWrong_ShouldRaiseException()
{
Should.Throw<Exception>(() =>
{
TypeAdapterConfig.GlobalSettings.ScanInheritedTypes(Assembly.GetExecutingAssembly());
});
}
}

public class WrongInheritedDestinationModel : IMapFrom<SourceModel>
{
public string Type { get; set; }
public int Value { get; set; }

public void ConfigureMapping()
{
TypeAdapterConfig.GlobalSettings
.NewConfig<SourceModel, InheritedDestinationModel>()
.Map(dest => dest.Value, _ => DesireValues.Number);
}
}
9 changes: 9 additions & 0 deletions src/Mapster/Interfaces/IMapFrom.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Mapster;

public interface IMapFrom<TSource>
{
public void ConfigureMapping(TypeAdapterConfig config)
{
config.NewConfig(typeof(TSource), GetType());
}
}
47 changes: 47 additions & 0 deletions src/Mapster/Utils/InterfaceDynamicMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Linq;
using System.Reflection;

namespace Mapster.Utils;

public class InterfaceDynamicMapper
{
private readonly TypeAdapterConfig _config;
private readonly Assembly _assembly;

public InterfaceDynamicMapper(TypeAdapterConfig config, Assembly assembly)
{
_config = config;
_assembly = assembly;
}

internal void ApplyMappingFromAssembly()
{
var types = _assembly.GetTypes()
.Where(t =>
t.GetInterfaces()
.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)));

foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var method = GetMethod(type);
method!.Invoke(instance, new object[] { _config });
}
}

private static MethodInfo GetMethod(Type type)
{
const string methodName = "ConfigureMapping";
var method = type.GetMethod(methodName);
if (method == null) return type.GetInterface("IMapFrom`1")!.GetMethod(methodName)!;
var parameters = method.GetParameters();
var condition = parameters.Length == 1 && parameters[0].ParameterType == typeof(TypeAdapterConfig);
if (!condition)
{
throw new Exception($"{methodName} is not implemented right or it's ambiguous!");
}

return method;
}
}
12 changes: 12 additions & 0 deletions src/Mapster/Utils/TypeAdapterConfigExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Reflection;

namespace Mapster.Utils;

public static class TypeAdapterConfigExtensions
{
public static void ScanInheritedTypes(this TypeAdapterConfig config, Assembly assembly)
{
InterfaceDynamicMapper dynamicMapper = new(config, assembly);
dynamicMapper.ApplyMappingFromAssembly();
}
}

0 comments on commit 65b6466

Please sign in to comment.