tools/AutoMapper/MapperConfiguration.cs (391 lines of code) (raw):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper.Configuration;
using AutoMapper.Internal;
using AutoMapper.QueryableExtensions;
using AutoMapper.QueryableExtensions.Impl;
namespace AutoMapper
{
using static Expression;
using static ExpressionFactory;
using static Execution.ExpressionBuilder;
using UntypedMapperFunc = Func<object, object, ResolutionContext, object>;
using Validator = Action<ValidationContext>;
public class MapperConfiguration : IConfigurationProvider
{
private static readonly Type[] ExcludedTypes = { typeof(object), typeof(ValueType), typeof(Enum) };
private static readonly ConstructorInfo ExceptionConstructor = typeof(AutoMapperMappingException).GetDeclaredConstructors().Single(c => c.GetParameters().Length == 3);
private readonly IEnumerable<IObjectMapper> _mappers;
private readonly TypeMapRegistry _typeMapRegistry = new TypeMapRegistry();
private LockingConcurrentDictionary<TypePair, TypeMap> _typeMapPlanCache;
private readonly LockingConcurrentDictionary<MapRequest, MapperFuncs> _mapPlanCache;
private readonly ConfigurationValidator _validator;
private readonly MapperConfigurationExpressionValidator _expressionValidator;
public MapperConfiguration(MapperConfigurationExpression configurationExpression)
{
_mappers = configurationExpression.Mappers.ToArray();
_typeMapPlanCache = new LockingConcurrentDictionary<TypePair, TypeMap>(GetTypeMap);
_mapPlanCache = new LockingConcurrentDictionary<MapRequest, MapperFuncs>(CreateMapperFuncs);
Validators = configurationExpression.Advanced.GetValidators();
_validator = new ConfigurationValidator(this);
_expressionValidator = new MapperConfigurationExpressionValidator(configurationExpression);
ExpressionBuilder = new ExpressionBuilder(this);
ServiceCtor = configurationExpression.ServiceCtor;
EnableNullPropagationForQueryMapping = configurationExpression.EnableNullPropagationForQueryMapping ?? false;
MaxExecutionPlanDepth = configurationExpression.Advanced.MaxExecutionPlanDepth + 1;
Configuration = new ProfileMap(configurationExpression);
Profiles = new[] { Configuration }.Concat(configurationExpression.Profiles.Select(p => new ProfileMap(p, configurationExpression))).ToArray();
foreach (var beforeSealAction in configurationExpression.Advanced.BeforeSealActions)
beforeSealAction?.Invoke(this);
Seal();
}
public MapperConfiguration(Action<IMapperConfigurationExpression> configure)
: this(Build(configure))
{
}
public void Validate(ValidationContext context)
{
foreach (var validator in Validators)
{
validator(context);
}
}
private Validator[] Validators { get; }
public IExpressionBuilder ExpressionBuilder { get; }
public Func<Type, object> ServiceCtor { get; }
public bool EnableNullPropagationForQueryMapping { get; }
public int MaxExecutionPlanDepth { get; }
private ProfileMap Configuration { get; }
public IEnumerable<ProfileMap> Profiles { get; }
public Func<TSource, TDestination, ResolutionContext, TDestination> GetMapperFunc<TSource, TDestination>(TypePair types)
{
var key = new TypePair(typeof(TSource), typeof(TDestination));
var mapRequest = new MapRequest(key, types);
return GetMapperFunc<TSource, TDestination>(mapRequest);
}
public Func<TSource, TDestination, ResolutionContext, TDestination> GetMapperFunc<TSource, TDestination>(MapRequest mapRequest)
=> (Func<TSource, TDestination, ResolutionContext, TDestination>)GetMapperFunc(mapRequest);
public void CompileMappings()
{
foreach (var request in _typeMapPlanCache.Keys.Select(types => new MapRequest(types, types)).ToArray())
{
GetMapperFunc(request);
}
}
public Delegate GetMapperFunc(MapRequest mapRequest) => _mapPlanCache.GetOrAdd(mapRequest).Typed;
public UntypedMapperFunc GetUntypedMapperFunc(MapRequest mapRequest) => _mapPlanCache.GetOrAdd(mapRequest).Untyped;
private MapperFuncs CreateMapperFuncs(MapRequest mapRequest) => new MapperFuncs(mapRequest, BuildExecutionPlan(mapRequest));
public LambdaExpression BuildExecutionPlan(Type sourceType, Type destinationType)
{
var typePair = new TypePair(sourceType, destinationType);
return BuildExecutionPlan(new MapRequest(typePair, typePair));
}
public LambdaExpression BuildExecutionPlan(MapRequest mapRequest)
{
var typeMap = ResolveTypeMap(mapRequest.RuntimeTypes, mapRequest.InlineConfig) ?? ResolveTypeMap(mapRequest.RequestedTypes, mapRequest.InlineConfig);
if (typeMap != null)
{
return GenerateTypeMapExpression(mapRequest, typeMap);
}
var mapperToUse = FindMapper(mapRequest.RuntimeTypes);
return GenerateObjectMapperExpression(mapRequest, mapperToUse, this);
}
private static LambdaExpression GenerateTypeMapExpression(MapRequest mapRequest, TypeMap typeMap)
{
var mapExpression = typeMap.MapExpression;
var typeMapSourceParameter = mapExpression.Parameters[0];
var typeMapDestinationParameter = mapExpression.Parameters[1];
var requestedSourceType = mapRequest.RequestedTypes.SourceType;
var requestedDestinationType = mapRequest.RequestedTypes.DestinationType;
if (typeMapSourceParameter.Type != requestedSourceType || typeMapDestinationParameter.Type != requestedDestinationType)
{
var requestedSourceParameter = Parameter(requestedSourceType, "source");
var requestedDestinationParameter = Parameter(requestedDestinationType, "typeMapDestination");
var contextParameter = Parameter(typeof(ResolutionContext), "context");
mapExpression = Lambda(ToType(Invoke(typeMap.MapExpression,
ToType(requestedSourceParameter, typeMapSourceParameter.Type),
ToType(requestedDestinationParameter, typeMapDestinationParameter.Type),
contextParameter
), mapRequest.RuntimeTypes.DestinationType),
requestedSourceParameter, requestedDestinationParameter, contextParameter);
}
return mapExpression;
}
private LambdaExpression GenerateObjectMapperExpression(MapRequest mapRequest, IObjectMapper mapperToUse, MapperConfiguration mapperConfiguration)
{
var destinationType = mapRequest.RequestedTypes.DestinationType;
var source = Parameter(mapRequest.RequestedTypes.SourceType, "source");
var destination = Parameter(destinationType, "mapperDestination");
var context = Parameter(typeof(ResolutionContext), "context");
Expression fullExpression;
if (mapperToUse == null)
{
var message = Constant("Missing type map configuration or unsupported mapping.");
fullExpression = Block(Throw(New(ExceptionConstructor, message, Constant(null, typeof(Exception)), Constant(mapRequest.RequestedTypes))), Default(destinationType));
}
else
{
var map = mapperToUse.MapExpression(mapperConfiguration, Configuration, null,
ToType(source, mapRequest.RuntimeTypes.SourceType),
ToType(destination, mapRequest.RuntimeTypes.DestinationType),
context);
var exception = Parameter(typeof(Exception), "ex");
fullExpression =
TryCatch(ToType(map, destinationType),
MakeCatchBlock(typeof(Exception), exception, Block(
Throw(New(ExceptionConstructor, Constant("Error mapping types."), exception, Constant(mapRequest.RequestedTypes))),
Default(destination.Type)), null));
}
var nullCheckSource = NullCheckSource(Configuration, source, destination, fullExpression);
return Lambda(nullCheckSource, source, destination, context);
}
public TypeMap[] GetAllTypeMaps() => _typeMapRegistry.TypeMaps.ToArray();
public TypeMap FindTypeMapFor(Type sourceType, Type destinationType) => FindTypeMapFor(new TypePair(sourceType, destinationType));
public TypeMap FindTypeMapFor<TSource, TDestination>() => FindTypeMapFor(new TypePair(typeof(TSource), typeof(TDestination)));
public TypeMap FindTypeMapFor(TypePair typePair) => _typeMapRegistry.GetTypeMap(typePair);
public TypeMap ResolveTypeMap(Type sourceType, Type destinationType)
{
var typePair = new TypePair(sourceType, destinationType);
return ResolveTypeMap(typePair, new DefaultTypeMapConfig(typePair));
}
public TypeMap ResolveTypeMap(Type sourceType, Type destinationType, ITypeMapConfiguration inlineConfiguration)
{
var typePair = new TypePair(sourceType, destinationType);
return ResolveTypeMap(typePair, inlineConfiguration);
}
public TypeMap ResolveTypeMap(TypePair typePair)
=> ResolveTypeMap(typePair, new DefaultTypeMapConfig(typePair));
public TypeMap ResolveTypeMap(TypePair typePair, ITypeMapConfiguration inlineConfiguration)
{
var typeMap = _typeMapPlanCache.GetOrAdd(typePair);
// if it's a dynamically created type map, we need to seal it outside GetTypeMap to handle recursion
if (typeMap != null && typeMap.MapExpression == null && _typeMapRegistry.GetTypeMap(typePair) == null)
{
lock (typeMap)
{
inlineConfiguration.Configure(typeMap);
typeMap.Seal(this);
}
}
return typeMap;
}
private TypeMap GetTypeMap(TypePair initialTypes)
{
var doesNotHaveMapper = FindMapper(initialTypes) == null;
foreach (var types in initialTypes.GetRelatedTypePairs())
{
if (types != initialTypes && _typeMapPlanCache.TryGetValue(types, out var typeMap))
{
if (typeMap != null)
{
return typeMap;
}
}
typeMap = FindTypeMapFor(types);
if (typeMap != null)
{
return typeMap;
}
typeMap = FindClosedGenericTypeMapFor(types);
if (typeMap != null)
{
return typeMap;
}
if (doesNotHaveMapper)
{
typeMap = FindConventionTypeMapFor(types);
if (typeMap != null)
{
return typeMap;
}
}
}
if (doesNotHaveMapper
&& Configuration.CreateMissingTypeMaps
&& !(initialTypes.SourceType.IsAbstract() && initialTypes.SourceType.IsClass())
&& !(initialTypes.DestinationType.IsAbstract() && initialTypes.DestinationType.IsClass())
&& !ExcludedTypes.Contains(initialTypes.SourceType)
&& !ExcludedTypes.Contains(initialTypes.DestinationType))
{
lock (this)
{
return Configuration.CreateInlineMap(_typeMapRegistry, initialTypes);
}
}
return null;
}
public void AssertConfigurationIsValid(TypeMap typeMap)
{
_validator.AssertConfigurationIsValid(Enumerable.Repeat(typeMap, 1));
}
public void AssertConfigurationIsValid(string profileName)
{
_validator.AssertConfigurationIsValid(_typeMapRegistry.TypeMaps.Where(typeMap => typeMap.Profile.Name == profileName));
}
public void AssertConfigurationIsValid<TProfile>()
where TProfile : Profile, new()
{
AssertConfigurationIsValid(new TProfile().ProfileName);
}
public void AssertConfigurationIsValid()
{
_expressionValidator.AssertConfigurationExpressionIsValid();
_validator.AssertConfigurationIsValid(_typeMapRegistry.TypeMaps.Where(tm => !tm.SourceType.IsGenericTypeDefinition() && !tm.DestinationType.IsGenericTypeDefinition()));
}
public IMapper CreateMapper() => new Mapper(this);
public IMapper CreateMapper(Func<Type, object> serviceCtor) => new Mapper(this, serviceCtor);
public IEnumerable<IObjectMapper> GetMappers() => _mappers;
private static MapperConfigurationExpression Build(Action<IMapperConfigurationExpression> configure)
{
var expr = new MapperConfigurationExpression();
configure(expr);
return expr;
}
private void Seal()
{
var derivedMaps = new List<Tuple<TypePair, TypeMap>>();
var redirectedTypes = new List<Tuple<TypePair, TypePair>>();
foreach (var profile in Profiles)
{
profile.Register(_typeMapRegistry);
}
foreach (var profile in Profiles)
{
profile.Configure(_typeMapRegistry);
}
foreach (var typeMap in _typeMapRegistry.TypeMaps)
{
_typeMapPlanCache[typeMap.Types] = typeMap;
if (typeMap.DestinationTypeOverride != null)
{
redirectedTypes.Add(Tuple.Create(typeMap.Types, new TypePair(typeMap.SourceType, typeMap.DestinationTypeOverride)));
}
derivedMaps.AddRange(GetDerivedTypeMaps(typeMap).Select(derivedMap => Tuple.Create(new TypePair(derivedMap.SourceType, typeMap.DestinationType), derivedMap)));
}
foreach (var redirectedType in redirectedTypes)
{
var derivedMap = FindTypeMapFor(redirectedType.Item2);
if (derivedMap != null)
{
_typeMapPlanCache[redirectedType.Item1] = derivedMap;
}
}
foreach (var derivedMap in derivedMaps.Where(derivedMap => !_typeMapPlanCache.ContainsKey(derivedMap.Item1)))
{
_typeMapPlanCache[derivedMap.Item1] = derivedMap.Item2;
}
foreach (var typeMap in _typeMapRegistry.TypeMaps)
{
typeMap.Seal(this);
}
}
private IEnumerable<TypeMap> GetDerivedTypeMaps(TypeMap typeMap)
{
foreach (var derivedTypes in typeMap.IncludedDerivedTypes)
{
var derivedMap = FindTypeMapFor(derivedTypes);
if (derivedMap == null)
{
throw QueryMapperHelper.MissingMapException(derivedTypes.SourceType, derivedTypes.DestinationType);
}
yield return derivedMap;
foreach (var derivedTypeMap in GetDerivedTypeMaps(derivedMap))
{
yield return derivedTypeMap;
}
}
}
private TypeMap FindConventionTypeMapFor(TypePair typePair)
{
var profile = Profiles.FirstOrDefault(p => p.IsConventionMap(typePair));
if(profile == null)
{
return null;
}
TypeMap typeMap;
lock(this)
{
typeMap = profile.CreateConventionTypeMap(_typeMapRegistry, typePair);
}
return typeMap;
}
private TypeMap FindClosedGenericTypeMapFor(TypePair typePair)
{
if(typePair.GetOpenGenericTypePair() == null)
{
return null;
}
var mapInfo = Profiles.Select(p => new { GenericMap = p.GetGenericMap(typePair), Profile = p }).FirstOrDefault(p => p.GenericMap != null);
if(mapInfo == null)
{
return null;
}
TypeMap typeMap;
lock(this)
{
typeMap = mapInfo.Profile.CreateClosedGenericTypeMap(mapInfo.GenericMap, _typeMapRegistry, typePair);
}
return typeMap;
}
public IObjectMapper FindMapper(TypePair types) =>_mappers.FirstOrDefault(m => m.IsMatch(types));
internal struct MapperFuncs
{
public Delegate Typed { get; }
public UntypedMapperFunc Untyped { get; }
public MapperFuncs(MapRequest mapRequest, LambdaExpression typedExpression)
{
Typed = typedExpression.Compile();
Untyped = Wrap(mapRequest, Typed).Compile();
}
private static Expression<UntypedMapperFunc> Wrap(MapRequest mapRequest, Delegate typedDelegate)
{
var sourceParameter = Parameter(typeof(object), "source");
var destinationParameter = Parameter(typeof(object), "destination");
var contextParameter = Parameter(typeof(ResolutionContext), "context");
var requestedSourceType = mapRequest.RequestedTypes.SourceType;
var requestedDestinationType = mapRequest.RequestedTypes.DestinationType;
var destination = requestedDestinationType.IsValueType() ? Coalesce(destinationParameter, New(requestedDestinationType)) : (Expression)destinationParameter;
// Invoking a delegate here
return Lambda<UntypedMapperFunc>(
ToType(
Invoke(Constant(typedDelegate), ToType(sourceParameter, requestedSourceType), ToType(destination, requestedDestinationType), contextParameter)
, typeof(object)),
sourceParameter, destinationParameter, contextParameter);
}
}
internal class DefaultTypeMapConfig : ITypeMapConfiguration
{
public DefaultTypeMapConfig(TypePair types)
{
Types = types;
}
public void Configure(TypeMap typeMap) { }
public Type SourceType => Types.SourceType;
public Type DestinationType => Types.DestinationType;
public bool IsOpenGeneric => false;
public TypePair Types { get; }
public ITypeMapConfiguration ReverseTypeMap => null;
}
}
public struct ValidationContext
{
public IObjectMapper ObjectMapper { get; }
public PropertyMap PropertyMap { get; }
public TypeMap TypeMap { get; }
public TypePair Types { get; }
public ValidationContext(TypePair types, PropertyMap propertyMap, IObjectMapper objectMapper) : this(types, propertyMap, objectMapper, null)
{
}
public ValidationContext(TypePair types, PropertyMap propertyMap, TypeMap typeMap) : this(types, propertyMap, null, typeMap)
{
}
private ValidationContext(TypePair types, PropertyMap propertyMap, IObjectMapper objectMapper, TypeMap typeMap)
{
ObjectMapper = objectMapper;
TypeMap = typeMap;
Types = types;
PropertyMap = propertyMap;
}
}
}