@ -1,5 +1,11 @@
using Microsoft.Extensions.DependencyInjection ;
using System ;
using System.Collections ;
using System.Collections.Concurrent ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Linq ;
using System.Reflection ;
using Volo.Abp.DependencyInjection ;
namespace Volo.Abp.ObjectMapping ;
@ -19,6 +25,8 @@ public class DefaultObjectMapper<TContext> : DefaultObjectMapper, IObjectMapper<
public class DefaultObjectMapper : IObjectMapper , ITransientDependency
{
protected static ConcurrentDictionary < string , MethodInfo > MethodInfoCache { get ; } = new ConcurrentDictionary < string , MethodInfo > ( ) ;
public IAutoObjectMappingProvider AutoObjectMappingProvider { get ; }
protected IServiceProvider ServiceProvider { get ; }
@ -46,6 +54,12 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency
{
return specificMapper . Map ( source ) ;
}
var result = TryToMapCollection < TSource , TDestination > ( scope , source , default ) ;
if ( result ! = null )
{
return result ;
}
}
if ( source is IMapTo < TDestination > mapperSource )
@ -85,6 +99,12 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency
{
return specificMapper . Map ( source , destination ) ;
}
var result = TryToMapCollection ( scope , source , destination ) ;
if ( result ! = null )
{
return result ;
}
}
if ( source is IMapTo < TDestination > mapperSource )
@ -102,6 +122,131 @@ public class DefaultObjectMapper : IObjectMapper, ITransientDependency
return AutoMap ( source , destination ) ;
}
protected virtual TDestination ? TryToMapCollection < TSource , TDestination > ( IServiceScope serviceScope , TSource source , TDestination ? destination )
{
if ( ! IsCollectionGenericType < TSource , TDestination > ( out var sourceArgumentType , out var destinationArgumentType , out var definitionGenericType ) )
{
return default ;
}
var mapperType = typeof ( IObjectMapper < , > ) . MakeGenericType ( sourceArgumentType , destinationArgumentType ) ;
var specificMapper = serviceScope . ServiceProvider . GetService ( mapperType ) ;
if ( specificMapper = = null )
{
//skip, no specific mapper
return default ;
}
var cacheKey = $"{mapperType.FullName}_{(destination == null ? " MapMethodWithSingleParameter " : " MapMethodWithDoubleParameters ")}" ;
var method = MethodInfoCache . GetOrAdd (
cacheKey ,
_ = >
{
return specificMapper
. GetType ( )
. GetMethods ( )
. First ( x = >
x . Name = = nameof ( IObjectMapper < object , object > . Map ) & &
x . GetParameters ( ) . Length = = ( destination = = null ? 1 : 2 )
) ;
}
) ;
var sourceList = source ! . As < IList > ( ) ;
var result = definitionGenericType . IsGenericType
? Activator . CreateInstance ( definitionGenericType . MakeGenericType ( destinationArgumentType ) ) ! . As < IList > ( )
: Array . CreateInstance ( destinationArgumentType , sourceList . Count ) ;
if ( destination ! = null & & ! destination . GetType ( ) . IsArray )
{
//Clear destination collection if destination not an array, We won't change array just same behavior as AutoMapper.
destination . As < IList > ( ) . Clear ( ) ;
}
for ( var i = 0 ; i < sourceList . Count ; i + + )
{
var invokeResult = destination = = null
? method . Invoke ( specificMapper , new [ ] { sourceList [ i ] } ) !
: method . Invoke ( specificMapper , new [ ] { sourceList [ i ] , Activator . CreateInstance ( destinationArgumentType ) ! } ) ! ;
if ( definitionGenericType . IsGenericType )
{
result . Add ( invokeResult ) ;
destination ? . As < IList > ( ) . Add ( invokeResult ) ;
}
else
{
result [ i ] = invokeResult ;
}
}
if ( destination ! = null & & destination . GetType ( ) . IsArray )
{
//Return the new collection if destination is an array, We won't change array just same behavior as AutoMapper.
return ( TDestination ) result ;
}
//Return the destination if destination exists. The parameter reference equals with return object.
return destination ? ? ( TDestination ) result ;
}
protected virtual bool IsCollectionGenericType < TSource , TDestination > ( out Type sourceArgumentType , out Type destinationArgumentType , out Type definitionGenericType )
{
sourceArgumentType = default ! ;
destinationArgumentType = default ! ;
definitionGenericType = default ! ;
if ( ( ! typeof ( TSource ) . IsGenericType & & ! typeof ( TSource ) . IsArray ) | |
( ! typeof ( TDestination ) . IsGenericType & & ! typeof ( TDestination ) . IsArray ) )
{
return false ;
}
var supportedCollectionTypes = new [ ]
{
typeof ( IEnumerable < > ) ,
typeof ( ICollection < > ) ,
typeof ( Collection < > ) ,
typeof ( IList < > ) ,
typeof ( List < > )
} ;
if ( typeof ( TSource ) . IsGenericType & & supportedCollectionTypes . Any ( x = > x = = typeof ( TSource ) . GetGenericTypeDefinition ( ) ) )
{
sourceArgumentType = typeof ( TSource ) . GenericTypeArguments [ 0 ] ;
}
if ( typeof ( TSource ) . IsArray )
{
sourceArgumentType = typeof ( TSource ) . GetElementType ( ) ! ;
}
if ( sourceArgumentType = = default ! )
{
return false ;
}
definitionGenericType = typeof ( List < > ) ;
if ( typeof ( TDestination ) . IsGenericType & & supportedCollectionTypes . Any ( x = > x = = typeof ( TDestination ) . GetGenericTypeDefinition ( ) ) )
{
destinationArgumentType = typeof ( TDestination ) . GenericTypeArguments [ 0 ] ;
if ( typeof ( TDestination ) . GetGenericTypeDefinition ( ) = = typeof ( ICollection < > ) | |
typeof ( TDestination ) . GetGenericTypeDefinition ( ) = = typeof ( Collection < > ) )
{
definitionGenericType = typeof ( Collection < > ) ;
}
}
if ( typeof ( TDestination ) . IsArray )
{
destinationArgumentType = typeof ( TDestination ) . GetElementType ( ) ! ;
definitionGenericType = typeof ( Array ) ;
}
return destinationArgumentType ! = default ! ;
}
protected virtual TDestination AutoMap < TSource , TDestination > ( object source )
{
return AutoObjectMappingProvider . Map < TSource , TDestination > ( source ) ;