Commit b48125db authored by 阿星Plus's avatar 阿星Plus

AutoMapper 扩展

parent 10eb6466
using AutoMapper;
using System;
namespace Plus.AutoMapper
{
/// <summary>
/// AutoMapAttribute
/// </summary>
public class AutoMapAttribute : AutoMapAttributeBase
{
public AutoMapAttribute(params Type[] targetTypes)
: base(targetTypes)
{
}
public override void CreateMap(IMapperConfigurationExpression configuration, Type type)
{
if (TargetTypes.IsNullOrEmpty())
{
return;
}
configuration.CreateAutoAttributeMaps(type, TargetTypes, MemberList.Source);
foreach (var targetType in TargetTypes)
{
configuration.CreateAutoAttributeMaps(targetType, new[] { type }, MemberList.Destination);
}
}
}
}
\ No newline at end of file
using AutoMapper;
using System;
namespace Plus.AutoMapper
{
/// <summary>
/// AutoMapAttributeBase
/// </summary>
public abstract class AutoMapAttributeBase : Attribute
{
public Type[] TargetTypes { get; private set; }
protected AutoMapAttributeBase(params Type[] targetTypes)
{
TargetTypes = targetTypes;
}
public abstract void CreateMap(IMapperConfigurationExpression configuration, Type type);
}
}
\ No newline at end of file
using AutoMapper;
namespace Plus.AutoMapper
{
/// <summary>
/// AutoMapExtensions
/// </summary>
public static class AutoMapExtensions
{
/// <summary>
/// Converts an object to another using AutoMapper library. Creates a new object of <typeparamref name="TDestination"/>.
/// There must be a mapping between objects before calling this method.
/// </summary>
/// <typeparam name="TDestination">Type of the destination object</typeparam>
/// <param name="source">Source object</param>
public static TDestination MapTo<TDestination>(this object source)
{
return Mapper.Map<TDestination>(source);
}
/// <summary>
/// Execute a mapping from the source object to the existing destination object
/// There must be a mapping between objects before calling this method.
/// </summary>
/// <typeparam name="TSource">Source type</typeparam>
/// <typeparam name="TDestination">Destination type</typeparam>
/// <param name="source">Source object</param>
/// <param name="destination">Destination object</param>
/// <returns></returns>
public static TDestination MapTo<TSource, TDestination>(this TSource source, TDestination destination)
{
return Mapper.Map(source, destination);
}
}
}
\ No newline at end of file
using AutoMapper;
using System;
namespace Plus.AutoMapper
{
/// <summary>
/// AutoMapFromAttribute
/// </summary>
public class AutoMapFromAttribute : AutoMapAttributeBase
{
public MemberList MemberList { get; set; } = MemberList.Destination;
public AutoMapFromAttribute(params Type[] targetTypes)
: base(targetTypes)
{
}
public AutoMapFromAttribute(MemberList memberList, params Type[] targetTypes)
: this(targetTypes)
{
MemberList = memberList;
}
public override void CreateMap(IMapperConfigurationExpression configuration, Type type)
{
if (TargetTypes.IsNullOrEmpty())
{
return;
}
foreach (var targetType in TargetTypes)
{
configuration.CreateAutoAttributeMaps(targetType, new[] { type }, MemberList);
}
}
}
}
\ No newline at end of file
using System;
namespace Plus.AutoMapper
{
/// <summary>
/// AutoMapKeyAttribute
/// </summary>
public class AutoMapKeyAttribute : Attribute
{
}
}
\ No newline at end of file
using AutoMapper;
using System;
namespace Plus.AutoMapper
{
/// <summary>
/// AutoMapToAttribute
/// </summary>
public class AutoMapToAttribute : AutoMapAttributeBase
{
public MemberList MemberList { get; set; } = MemberList.Source;
public AutoMapToAttribute(params Type[] targetTypes)
: base(targetTypes)
{
}
public AutoMapToAttribute(MemberList memberList, params Type[] targetTypes)
: this(targetTypes)
{
MemberList = memberList;
}
public override void CreateMap(IMapperConfigurationExpression configuration, Type type)
{
if (TargetTypes.IsNullOrEmpty())
{
return;
}
configuration.CreateAutoAttributeMaps(type, TargetTypes, MemberList);
}
}
}
\ No newline at end of file
using AutoMapper;
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace Plus.AutoMapper
{
/// <summary>
/// AutoMapperConfigurationExtensions
/// </summary>
internal static class AutoMapperConfigurationExtensions
{
private static readonly object SyncObj = new object();
public static void CreateAutoAttributeMaps(this IMapperConfigurationExpression configuration, Type type)
{
lock (SyncObj)
{
foreach (var autoMapAttribute in type.GetTypeInfo().GetCustomAttributes<AutoMapAttributeBase>())
{
autoMapAttribute.CreateMap(configuration, type);
}
}
}
public static void CreateAutoAttributeMaps(this IMapperConfigurationExpression configuration, Type type, Type[] targetTypes, MemberList memberList)
{
//Get all the properties in the source that have the AutoMapKeyAttribute
var sourceKeysPropertyInfo = type.GetProperties()
.Where(w => w.GetCustomAttribute<AutoMapKeyAttribute>() != null)
.Select(s => s).ToList();
foreach (var targetType in targetTypes)
{
if (!sourceKeysPropertyInfo.Any())
{
configuration.CreateMap(type, targetType, memberList);
continue;
}
BinaryExpression equalityComparer = null;
//In a lambda expression represent the source exemple : (source) => ...
ParameterExpression sourceParameterExpression = Expression.Parameter(type, "source");
//In a lambda expression represent the target exemple : (target) => ...
ParameterExpression targetParameterExpression = Expression.Parameter(targetType, "target");
//We could use multiple AutoMapKey to compare the determine equality
foreach (PropertyInfo propertyInfo in sourceKeysPropertyInfo)
{
//In a lambda expression represent a specfic property of a parameter exemple : (source) => source.Id
MemberExpression sourcePropertyExpression = Expression.Property(sourceParameterExpression, propertyInfo);
//Find the target a property with the same name to compare with
//Exemple if we have in source the attribut AutoMapKey on the Property Id we want to get Id in the target to compare agaisnt
var targetPropertyInfo = targetType.GetProperty(sourcePropertyExpression.Member.Name);
//It happen if the property with AutoMapKeyAttribute does not exist in target
if (targetPropertyInfo is null)
{
continue;
}
//In a lambda expression represent a specfic property of a parameter exemple : (target) => target.Id
MemberExpression targetPropertyExpression = Expression.Property(targetParameterExpression, targetPropertyInfo);
//Compare the property defined by AutoMapKey in the source agaisnt the same property in the target
//Exemple (source, target) => source.Id == target.Id
BinaryExpression equal = Expression.Equal(sourcePropertyExpression, targetPropertyExpression);
if (equalityComparer is null)
{
equalityComparer = equal;
}
else
{
//If we compare multiple key we want to make an and condition between
//Exemple : (source, target) => source.Email == target.Email && source.UserName == target.UserName
equalityComparer = Expression.And(equalityComparer, equal);
}
}
//If there is not match for AutoMapKey in the target
//In this case we add the default mapping
if (equalityComparer is null)
{
configuration.CreateMap(type, targetType, memberList);
continue;
}
//We need to make a generic type of Func<SourceType, TargetType, bool> to invoke later Expression.Lambda
var funcGenericType = typeof(Func<,,>).MakeGenericType(type, targetType, typeof(bool));
//Make a method info of Expression.Lambda<Func<SourceType, TargetType, bool>> to call later
var lambdaMethodInfo = typeof(Expression).GetMethod("Lambda", 2, 1).MakeGenericMethod(funcGenericType);
//Make the call to Expression.Lambda
var expressionLambdaResult = lambdaMethodInfo.Invoke(null, new object[] { equalityComparer, new ParameterExpression[] { sourceParameterExpression, targetParameterExpression } });
//Get the method info of IMapperConfigurationExpression.CreateMap<Source, Target>
var createMapMethodInfo = configuration.GetType().GetMethod("CreateMap", 1, 2).MakeGenericMethod(type, targetType);
//Make the call to configuration.CreateMap<Source, Target>().
var createMapResult = createMapMethodInfo.Invoke(configuration, new object[] { memberList });
var autoMapperCollectionAssembly = Assembly.Load("AutoMapper.Collection");
var autoMapperCollectionTypes = autoMapperCollectionAssembly.GetTypes();
var equalityComparisonGenericMethodInfo = autoMapperCollectionTypes
.Where(w => !w.IsGenericType && !w.IsNested)
.SelectMany(s => s.GetMethods()).Where(w => w.Name == "EqualityComparison")
.FirstOrDefault()
.MakeGenericMethod(type, targetType);
//Make the call to EqualityComparison
//Exemple configuration.CreateMap<Source, Target>().EqualityComparison((source, target) => source.Id == target.Id)
equalityComparisonGenericMethodInfo.Invoke(createMapResult, new object[] { createMapResult, expressionLambdaResult });
}
}
}
}
\ No newline at end of file
using AutoMapper;
using IObjectMapper = Plus.ObjectMapping.IObjectMapper;
namespace Plus.AutoMapper
{
/// <summary>
/// AutoMapperObjectMapper
/// </summary>
public class AutoMapperObjectMapper : IObjectMapper
{
private readonly IMapper _mapper;
public AutoMapperObjectMapper(IMapper mapper)
{
_mapper = mapper;
}
public TDestination Map<TDestination>(object source)
{
return _mapper.Map<TDestination>(source);
}
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
return _mapper.Map(source, destination);
}
}
}
\ No newline at end of file
using AutoMapper;
namespace Plus.AutoMapper
{
/// <summary>
/// IAutoMapRegistrar
/// </summary>
public interface IAutoMapRegistrar
{
void RegisterMaps(IMapperConfigurationExpression config);
}
}
\ No newline at end of file
using AutoMapper;
using System;
using System.Collections.Generic;
namespace Plus.AutoMapper
{
/// <summary>
/// IPlusAutoMapperConfiguration
/// </summary>
public interface IPlusAutoMapperConfiguration
{
List<Action<IMapperConfigurationExpression>> Configurators { get; }
/// <summary>
/// Use static <see cref="Mapper.Instance"/>.
/// Default: true.
/// </summary>
bool UseStaticMapper { get; set; }
}
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="8.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Plus.Extensions\Plus.Extensions.csproj" />
<ProjectReference Include="..\Plus\Plus.csproj" />
</ItemGroup>
</Project>
using AutoMapper;
using System;
using System.Collections.Generic;
namespace Plus.AutoMapper
{
/// <summary>
/// PlusAutoMapperConfiguration
/// </summary>
public class PlusAutoMapperConfiguration : IPlusAutoMapperConfiguration
{
public List<Action<IMapperConfigurationExpression>> Configurators { get; }
public bool UseStaticMapper { get; set; }
public PlusAutoMapperConfiguration()
{
UseStaticMapper = true;
Configurators = new List<Action<IMapperConfigurationExpression>>();
}
}
}
\ No newline at end of file
using Plus.Configuration.Startup;
namespace Plus.AutoMapper
{
/// <summary>
/// PlusAutoMapperConfigurationExtensions
/// </summary>
public static class PlusAutoMapperConfigurationExtensions
{
public static IPlusAutoMapperConfiguration PlusAutoMapper(this IPlusStartupConfiguration configuration)
{
return configuration.Get<IPlusAutoMapperConfiguration>();
}
}
}
\ No newline at end of file
using AutoMapper;
using Castle.MicroKernel.Registration;
using Plus.Configuration.Startup;
using Plus.Modules;
using Plus.Reflection;
using System;
using System.Reflection;
using IObjectMapper = Plus.ObjectMapping.IObjectMapper;
namespace Plus.AutoMapper
{
/// <summary>
/// PluseAutoMapperModule
/// </summary>
[DependsOn(typeof(PlusLeadershipModule))]
public class PluseAutoMapperModule : PlusModule
{
private readonly ITypeFinder _typeFinder;
private static volatile bool _createdMappingsBefore;
private static readonly object SyncObj = new object();
public PluseAutoMapperModule(ITypeFinder typeFinder)
{
_typeFinder = typeFinder;
}
public override void PreInitialize()
{
IocManager.Register<IPlusAutoMapperConfiguration, PlusAutoMapperConfiguration>();
Configuration.ReplaceService<IObjectMapper, AutoMapperObjectMapper>();
}
public override void PostInitialize()
{
CreateMappings();
}
private void CreateMappings()
{
lock (SyncObj)
{
Action<IMapperConfigurationExpression> configurer = configuration =>
{
FindAndAutoMapTypes(configuration);
foreach (var configurator in Configuration.PlusAutoMapper().Configurators)
{
configurator(configuration);
}
};
if (Configuration.PlusAutoMapper().UseStaticMapper)
{
// 防止重复映射
if (!_createdMappingsBefore)
{
Mapper.Initialize(configurer);
_createdMappingsBefore = true;
}
IocManager.IocContainer.Register(
Component.For<IConfigurationProvider>().Instance(Mapper.Configuration).LifestyleSingleton()
);
IocManager.IocContainer.Register(
Component.For<IMapper>().Instance(Mapper.Instance).LifestyleSingleton()
);
}
else
{
var config = new MapperConfiguration(configurer);
IocManager.IocContainer.Register(
Component.For<IConfigurationProvider>().Instance(config).LifestyleSingleton()
);
IocManager.IocContainer.Register(
Component.For<IMapper>().Instance(config.CreateMapper()).LifestyleSingleton()
);
}
}
}
private void FindAndAutoMapTypes(IMapperConfigurationExpression configuration)
{
var types = _typeFinder.Find(type =>
{
var typeInfo = type.GetTypeInfo();
return type.IsDefined(typeof(AutoMapAttribute)) ||
type.IsDefined(typeof(AutoMapFromAttribute)) ||
type.IsDefined(typeof(AutoMapToAttribute));
});
Logger.DebugFormat("Found {0} classes define auto mapping attributes", types.Length);
foreach (var type in types)
{
Logger.Debug(type.FullName);
configuration.CreateAutoAttributeMaps(type);
}
}
}
}
\ No newline at end of file
using Plus.Dependency;
using System;
namespace Plus.Configuration.Startup
{
/// <summary>
/// PlusStartupConfigurationExtensions
/// </summary>
public static class PlusStartupConfigurationExtensions
{
public static void ReplaceService(this IPlusStartupConfiguration configuration, Type type, Type impl, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton)
{
configuration.ReplaceService(type, delegate
{
configuration.IocManager.Register(type, impl, lifeStyle);
});
}
public static void ReplaceService<TType, TImpl>(this IPlusStartupConfiguration configuration, DependencyLifeStyle lifeStyle = DependencyLifeStyle.Singleton) where TType : class where TImpl : class, TType
{
configuration.ReplaceService(typeof(TType), delegate
{
configuration.IocManager.Register<TType, TImpl>(lifeStyle);
});
}
public static void ReplaceService<TType>(this IPlusStartupConfiguration configuration, Action replaceAction) where TType : class
{
configuration.ReplaceService(typeof(TType), replaceAction);
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment