tools/AutoMapper/TypeMap.cs (307 lines of code) (raw):

using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using AutoMapper.Configuration; using AutoMapper.Execution; namespace AutoMapper { using Internal; /// <summary> /// Main configuration object holding all mapping configuration for a source and destination type /// </summary> [DebuggerDisplay("{SourceType.Name} -> {DestinationType.Name}")] public class TypeMap { private readonly List<LambdaExpression> _afterMapActions = new List<LambdaExpression>(); private readonly List<LambdaExpression> _beforeMapActions = new List<LambdaExpression>(); private readonly HashSet<TypePair> _includedDerivedTypes = new HashSet<TypePair>(); private readonly HashSet<TypePair> _includedBaseTypes = new HashSet<TypePair>(); private readonly List<PropertyMap> _propertyMaps = new List<PropertyMap>(); private readonly List<PathMap> _pathMaps = new List<PathMap>(); private readonly List<SourceMemberConfig> _sourceMemberConfigs = new List<SourceMemberConfig>(); private readonly IList<PropertyMap> _inheritedMaps = new List<PropertyMap>(); private PropertyMap[] _orderedPropertyMaps; private bool _sealed; private readonly IList<TypeMap> _inheritedTypeMaps = new List<TypeMap>(); private readonly List<ValueTransformerConfiguration> _valueTransformerConfigs = new List<ValueTransformerConfiguration>(); public TypeMap(TypeDetails sourceType, TypeDetails destinationType, ProfileMap profile) { SourceTypeDetails = sourceType; DestinationTypeDetails = destinationType; Types = new TypePair(sourceType.Type, destinationType.Type); Profile = profile; } public PathMap FindOrCreatePathMapFor(LambdaExpression destinationExpression, MemberPath path, TypeMap typeMap) { var pathMap = _pathMaps.SingleOrDefault(p => p.MemberPath == path); if(pathMap == null) { pathMap = new PathMap(destinationExpression, path, typeMap); _pathMaps.Add(pathMap); } return pathMap; } public PathMap FindPathMapByDestinationPath(string destinationFullPath) => PathMaps.SingleOrDefault(item => string.Join(".", item.MemberPath.Members.Select(m => m.Name)) == destinationFullPath); public LambdaExpression MapExpression { get; private set; } public TypePair Types { get; } public ConstructorMap ConstructorMap { get; set; } public TypeDetails SourceTypeDetails { get; } public TypeDetails DestinationTypeDetails { get; } public Type SourceType => SourceTypeDetails.Type; public Type DestinationType => DestinationTypeDetails.Type; public ProfileMap Profile { get; } public LambdaExpression CustomMapper { get; set; } public LambdaExpression CustomProjection { get; set; } public LambdaExpression DestinationCtor { get; set; } public Type DestinationTypeOverride { get; set; } public Type DestinationTypeToUse => DestinationTypeOverride ?? DestinationType; public bool ConstructDestinationUsingServiceLocator { get; set; } public MemberList ConfiguredMemberList { get; set; } public IEnumerable<TypePair> IncludedDerivedTypes => _includedDerivedTypes; public IEnumerable<TypePair> IncludedBaseTypes => _includedBaseTypes; public IEnumerable<LambdaExpression> BeforeMapActions => _beforeMapActions; public IEnumerable<LambdaExpression> AfterMapActions => _afterMapActions; public IEnumerable<ValueTransformerConfiguration> ValueTransformers => _valueTransformerConfigs; public bool PreserveReferences { get; set; } public LambdaExpression Condition { get; set; } public int MaxDepth { get; set; } public LambdaExpression Substitution { get; set; } public LambdaExpression ConstructExpression { get; set; } public Type TypeConverterType { get; set; } public bool DisableConstructorValidation { get; set; } public PropertyMap[] GetPropertyMaps() => _orderedPropertyMaps ?? _propertyMaps.Concat(_inheritedMaps).ToArray(); public IEnumerable<PathMap> PathMaps => _pathMaps; public bool IsConventionMap { get; set; } public bool? IsValid { get; set; } public bool ConstructorParameterMatches(string destinationPropertyName) => ConstructorMap?.CtorParams.Any(c => !c.DefaultValue && string.Equals(c.Parameter.Name, destinationPropertyName, StringComparison.OrdinalIgnoreCase)) == true; public void AddPropertyMap(MemberInfo destProperty, IEnumerable<MemberInfo> resolvers) { var propertyMap = new PropertyMap(destProperty, this); propertyMap.ChainMembers(resolvers); _propertyMaps.Add(propertyMap); } public string[] GetUnmappedPropertyNames() { string GetPropertyName(PropertyMap pm) => ConfiguredMemberList == MemberList.Destination ? pm.DestinationProperty.Name : pm.SourceMember != null ? pm.SourceMember.Name : pm.DestinationProperty.Name; string[] GetPropertyNames(IEnumerable<PropertyMap> propertyMaps) => propertyMaps.Where(pm => pm.IsMapped()).Select(GetPropertyName).ToArray(); var autoMappedProperties = GetPropertyNames(_propertyMaps); var inheritedProperties = GetPropertyNames(_inheritedMaps); IEnumerable<string> properties; if(ConfiguredMemberList == MemberList.Destination) { properties = DestinationTypeDetails.PublicWriteAccessors .Select(p => p.Name) .Except(autoMappedProperties) .Except(inheritedProperties); } else { var redirectedSourceMembers = _propertyMaps .Where(pm => pm.IsMapped() && pm.SourceMember != null && pm.SourceMember.Name != pm.DestinationProperty.Name) .Select(pm => pm.SourceMember.Name); var ignoredSourceMembers = _sourceMemberConfigs .Where(smc => smc.IsIgnored()) .Select(pm => pm.SourceMember.Name).ToList(); properties = SourceTypeDetails.PublicReadAccessors .Select(p => p.Name) .Except(autoMappedProperties) .Except(inheritedProperties) .Except(redirectedSourceMembers) .Except(ignoredSourceMembers); } return properties.Where(memberName => !Profile.GlobalIgnores.Any(memberName.StartsWith)).ToArray(); } public bool PassesCtorValidation() { if (DisableConstructorValidation) return true; if (DestinationCtor != null) return true; if (ConstructDestinationUsingServiceLocator) return true; if (ConstructorMap?.CanResolve == true) return true; if (DestinationTypeToUse.IsInterface()) return true; if (DestinationTypeToUse.IsAbstract()) return true; if (DestinationTypeToUse.IsGenericTypeDefinition()) return true; if (DestinationTypeToUse.IsValueType()) return true; var constructors = DestinationTypeToUse .GetDeclaredConstructors() .Where(ci => !ci.IsStatic); //find a ctor with only optional args var ctorWithOptionalArgs = constructors.FirstOrDefault(c => c.GetParameters().All(p => p.IsOptional)); return ctorWithOptionalArgs != null; } public PropertyMap FindOrCreatePropertyMapFor(MemberInfo destinationProperty) { var propertyMap = GetExistingPropertyMapFor(destinationProperty); if (propertyMap != null) return propertyMap; propertyMap = new PropertyMap(destinationProperty, this); _propertyMaps.Add(propertyMap); return propertyMap; } public void IncludeDerivedTypes(Type derivedSourceType, Type derivedDestinationType) { var derivedTypes = new TypePair(derivedSourceType, derivedDestinationType); if (derivedTypes.Equals(Types)) { throw new InvalidOperationException("You cannot include a type map into itself."); } _includedDerivedTypes.Add(derivedTypes); } public void IncludeBaseTypes(Type baseSourceType, Type baseDestinationType) { var baseTypes = new TypePair(baseSourceType, baseDestinationType); if (baseTypes.Equals(Types)) { throw new InvalidOperationException("You cannot include a type map into itself."); } _includedBaseTypes.Add(baseTypes); } internal void IgnorePaths(MemberInfo destinationMember) { foreach(var pathMap in _pathMaps.Where(pm => pm.MemberPath.First == destinationMember)) { pathMap.Ignored = true; } } public Type GetDerivedTypeFor(Type derivedSourceType) { if (DestinationTypeOverride != null) { return DestinationTypeOverride; } // This might need to be fixed for multiple derived source types to different dest types var match = _includedDerivedTypes.FirstOrDefault(tp => tp.SourceType == derivedSourceType); return match.DestinationType ?? DestinationType; } public bool TypeHasBeenIncluded(TypePair derivedTypes) => _includedDerivedTypes.Contains(derivedTypes); public bool HasDerivedTypesToInclude() => _includedDerivedTypes.Any() || DestinationTypeOverride != null; public void AddBeforeMapAction(LambdaExpression beforeMap) { if(!_beforeMapActions.Contains(beforeMap)) { _beforeMapActions.Add(beforeMap); } } public void AddAfterMapAction(LambdaExpression afterMap) { if(!_afterMapActions.Contains(afterMap)) { _afterMapActions.Add(afterMap); } } public void AddValueTransformation(ValueTransformerConfiguration valueTransformerConfiguration) { _valueTransformerConfigs.Add(valueTransformerConfiguration); } public void Seal(IConfigurationProvider configurationProvider, Stack<TypeMap> typeMapsPath = null) { if(_sealed) { return; } _sealed = true; foreach (var inheritedTypeMap in _inheritedTypeMaps) { ApplyInheritedTypeMap(inheritedTypeMap); } _orderedPropertyMaps = _propertyMaps .Union(_inheritedMaps) .OrderBy(map => map.MappingOrder).ToArray(); MapExpression = new TypeMapPlanBuilder(configurationProvider, this).CreateMapperLambda(typeMapsPath); } public PropertyMap GetExistingPropertyMapFor(MemberInfo destinationProperty) { if (!destinationProperty.DeclaringType.IsAssignableFrom(DestinationType)) return null; var propertyMap = _propertyMaps.FirstOrDefault(pm => pm.DestinationProperty.Name.Equals(destinationProperty.Name)); if (propertyMap != null) return propertyMap; propertyMap = _inheritedMaps.FirstOrDefault(pm => pm.DestinationProperty.Name.Equals(destinationProperty.Name)); if (propertyMap == null) return null; var propertyInfo = propertyMap.DestinationProperty as PropertyInfo; if (propertyInfo == null) return propertyMap; var baseAccessor = propertyInfo.GetGetMethod(); if (baseAccessor.IsAbstract || baseAccessor.IsVirtual) return propertyMap; var accessor = ((PropertyInfo)destinationProperty).GetGetMethod(); if (baseAccessor.DeclaringType == accessor.DeclaringType) return propertyMap; return null; } public void InheritTypes(TypeMap inheritedTypeMap) { foreach (var includedDerivedType in inheritedTypeMap._includedDerivedTypes .Where(includedDerivedType => !_includedDerivedTypes.Contains(includedDerivedType))) { _includedDerivedTypes.Add(includedDerivedType); } } public SourceMemberConfig FindOrCreateSourceMemberConfigFor(MemberInfo sourceMember) { var config = _sourceMemberConfigs.FirstOrDefault(smc => Equals(smc.SourceMember, sourceMember)); if (config != null) return config; config = new SourceMemberConfig(sourceMember); _sourceMemberConfigs.Add(config); return config; } public void AddInheritedMap(TypeMap inheritedTypeMap) { _inheritedTypeMaps.Add(inheritedTypeMap); } public bool ShouldCheckForValid() => CustomMapper == null && CustomProjection == null && TypeConverterType == null && DestinationTypeOverride == null && ConfiguredMemberList != MemberList.None && !(IsValid ?? false); private void ApplyInheritedTypeMap(TypeMap inheritedTypeMap) { foreach (var inheritedMappedProperty in inheritedTypeMap.GetPropertyMaps().Where(m => m.IsMapped())) { var conventionPropertyMap = GetPropertyMaps() .SingleOrDefault(m => m.DestinationProperty.Name == inheritedMappedProperty.DestinationProperty.Name); if (conventionPropertyMap != null) { conventionPropertyMap.ApplyInheritedPropertyMap(inheritedMappedProperty); } else { var propertyMap = new PropertyMap(inheritedMappedProperty, this); _inheritedMaps.Add(propertyMap); } } //Include BeforeMap foreach (var beforeMapAction in inheritedTypeMap._beforeMapActions) { AddBeforeMapAction(beforeMapAction); } //Include AfterMap foreach (var afterMapAction in inheritedTypeMap._afterMapActions) { AddAfterMapAction(afterMapAction); } var notOverridenSourceConfigs = inheritedTypeMap._sourceMemberConfigs.Where( baseConfig => _sourceMemberConfigs.All(derivedConfig => derivedConfig.SourceMember != baseConfig.SourceMember)); _sourceMemberConfigs.AddRange(notOverridenSourceConfigs); var notOverridenPathMaps = inheritedTypeMap.PathMaps.Where( baseConfig => PathMaps.All(derivedConfig => derivedConfig.MemberPath != baseConfig.MemberPath)); _pathMaps.AddRange(notOverridenPathMaps); } } }