tools/AutoMapper/Configuration/MappingExpression.cs (512 lines of code) (raw):

using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using AutoMapper.Internal; using AutoMapper.Configuration.Internal; namespace AutoMapper.Configuration { using static Expression; public class MappingExpression : MappingExpression<object, object>, IMappingExpression { public MappingExpression(TypePair types, MemberList memberList) : base(memberList, types) { } public new IMappingExpression ReverseMap() => (IMappingExpression) base.ReverseMap(); public IMappingExpression Substitute(Func<object, object> substituteFunc) => (IMappingExpression) base.Substitute(substituteFunc); public new IMappingExpression ConstructUsingServiceLocator() => (IMappingExpression)base.ConstructUsingServiceLocator(); public void ForAllMembers(Action<IMemberConfigurationExpression> memberOptions) => base.ForAllMembers(opts => memberOptions((IMemberConfigurationExpression)opts)); void IMappingExpression.ConvertUsing<TTypeConverter>() => ConvertUsing(typeof(TTypeConverter)); public void ConvertUsing(Type typeConverterType) => TypeMapActions.Add(tm => tm.TypeConverterType = typeConverterType); public void ForAllOtherMembers(Action<IMemberConfigurationExpression> memberOptions) => base.ForAllOtherMembers(o => memberOptions((IMemberConfigurationExpression)o)); public IMappingExpression ForMember(string name, Action<IMemberConfigurationExpression> memberOptions) => (IMappingExpression)base.ForMember(name, c => memberOptions((IMemberConfigurationExpression)c)); public new IMappingExpression ForSourceMember(string sourceMemberName, Action<ISourceMemberConfigurationExpression> memberOptions) => (IMappingExpression)base.ForSourceMember(sourceMemberName, memberOptions); public new IMappingExpression Include(Type otherSourceType, Type otherDestinationType) => (IMappingExpression)base.Include(otherSourceType, otherDestinationType); public new IMappingExpression IgnoreAllPropertiesWithAnInaccessibleSetter() => (IMappingExpression)base.IgnoreAllPropertiesWithAnInaccessibleSetter(); public new IMappingExpression IgnoreAllSourcePropertiesWithAnInaccessibleSetter() => (IMappingExpression)base.IgnoreAllSourcePropertiesWithAnInaccessibleSetter(); public new IMappingExpression IncludeBase(Type sourceBase, Type destinationBase) => (IMappingExpression)base.IncludeBase(sourceBase, destinationBase); public new IMappingExpression BeforeMap(Action<object, object> beforeFunction) => (IMappingExpression)base.BeforeMap(beforeFunction); public new IMappingExpression BeforeMap<TMappingAction>() where TMappingAction : IMappingAction<object, object> => (IMappingExpression)base.BeforeMap<TMappingAction>(); public new IMappingExpression AfterMap(Action<object, object> afterFunction) => (IMappingExpression)base.AfterMap(afterFunction); public new IMappingExpression AfterMap<TMappingAction>() where TMappingAction : IMappingAction<object, object> => (IMappingExpression)base.AfterMap<TMappingAction>(); public new IMappingExpression ConstructUsing(Func<object, object> ctor) => (IMappingExpression)base.ConstructUsing(ctor); public new IMappingExpression ConstructUsing(Func<object, ResolutionContext, object> ctor) => (IMappingExpression)base.ConstructUsing(ctor); public IMappingExpression ConstructProjectionUsing(LambdaExpression ctor) { TypeMapActions.Add(tm => tm.ConstructExpression = ctor); return this; } public new IMappingExpression ValidateMemberList(MemberList memberList) => (IMappingExpression) base.ValidateMemberList(memberList); public new IMappingExpression MaxDepth(int depth) => (IMappingExpression)base.MaxDepth(depth); public new IMappingExpression ForCtorParam(string ctorParamName, Action<ICtorParamConfigurationExpression<object>> paramOptions) => (IMappingExpression)base.ForCtorParam(ctorParamName, paramOptions); public new IMappingExpression PreserveReferences() => (IMappingExpression)base.PreserveReferences(); protected override IPropertyMapConfiguration CreateMemberConfigurationExpression<TMember>(MemberInfo member, Type sourceType) => new MemberConfigurationExpression(member, sourceType); protected override MappingExpression<object, object> CreateReverseMapExpression() => new MappingExpression(new TypePair(DestinationType, SourceType), MemberList.Source); internal class MemberConfigurationExpression : MemberConfigurationExpression<object, object, object>, IMemberConfigurationExpression { public MemberConfigurationExpression(MemberInfo destinationMember, Type sourceType) : base(destinationMember, sourceType) { } public void ResolveUsing(Type valueResolverType) { var config = new ValueResolverConfiguration(valueResolverType, valueResolverType.GetGenericInterface(typeof(IValueResolver<,,>))); PropertyMapActions.Add(pm => pm.ValueResolverConfig = config); } public void ResolveUsing(Type valueResolverType, string memberName) { var config = new ValueResolverConfiguration(valueResolverType, valueResolverType.GetGenericInterface(typeof(IMemberValueResolver<,,,>))) { SourceMemberName = memberName }; PropertyMapActions.Add(pm => pm.ValueResolverConfig = config); } public void ResolveUsing<TSource, TDestination, TSourceMember, TDestMember>(IMemberValueResolver<TSource, TDestination, TSourceMember, TDestMember> resolver, string memberName) { var config = new ValueResolverConfiguration(resolver, typeof(IMemberValueResolver<TSource, TDestination, TSourceMember, TDestMember>)) { SourceMemberName = memberName }; PropertyMapActions.Add(pm => pm.ValueResolverConfig = config); } } } public class MappingExpression<TSource, TDestination> : IMappingExpression<TSource, TDestination>, ITypeMapConfiguration { private readonly List<IPropertyMapConfiguration> _memberConfigurations = new List<IPropertyMapConfiguration>(); private readonly List<SourceMappingExpression> _sourceMemberConfigurations = new List<SourceMappingExpression>(); private readonly List<CtorParamConfigurationExpression<TSource>> _ctorParamConfigurations = new List<CtorParamConfigurationExpression<TSource>>(); private readonly List<ValueTransformerConfiguration> _valueTransformers = new List<ValueTransformerConfiguration>(); private MappingExpression<TDestination, TSource> _reverseMap; private Action<IMemberConfigurationExpression<TSource, TDestination, object>> _allMemberOptions; private Func<MemberInfo, bool> _memberFilter; public MappingExpression(MemberList memberList) : this(memberList, typeof(TSource), typeof(TDestination)) { } public MappingExpression(MemberList memberList, Type sourceType, Type destinationType) : this(memberList, new TypePair(sourceType, destinationType)) { } public MappingExpression(MemberList memberList, TypePair types) { Types = types; IsOpenGeneric = types.SourceType.IsGenericTypeDefinition() || types.DestinationType.IsGenericTypeDefinition(); TypeMapActions.Add(tm => tm.ConfiguredMemberList = memberList); } public TypePair Types { get; } public Type SourceType => Types.SourceType; public Type DestinationType => Types.DestinationType; public bool IsOpenGeneric { get; } public ITypeMapConfiguration ReverseTypeMap => _reverseMap; public IList<ValueTransformerConfiguration> ValueTransformers => _valueTransformers; protected List<Action<TypeMap>> TypeMapActions { get; } = new List<Action<TypeMap>>(); public IMappingExpression<TSource, TDestination> PreserveReferences() { TypeMapActions.Add(tm => tm.PreserveReferences = true); return this; } protected virtual IPropertyMapConfiguration CreateMemberConfigurationExpression<TMember>(MemberInfo member, Type sourceType) => new MemberConfigurationExpression<TSource, TDestination, TMember>(member, sourceType); protected virtual MappingExpression<TDestination, TSource> CreateReverseMapExpression() => new MappingExpression<TDestination, TSource>(MemberList.None, DestinationType, SourceType); public IMappingExpression<TSource, TDestination> ForPath<TMember>(Expression<Func<TDestination, TMember>> destinationMember, Action<IPathConfigurationExpression<TSource, TDestination, TMember>> memberOptions) { if(!destinationMember.IsMemberPath()) { throw new ArgumentOutOfRangeException(nameof(destinationMember), "Only member accesses are allowed."); } var expression = new PathConfigurationExpression<TSource, TDestination, TMember>(destinationMember); var firstMember = expression.MemberPath.First; var firstMemberConfig = GetDestinationMemberConfiguration(firstMember); if(firstMemberConfig == null) { IgnoreDestinationMember(firstMember, ignorePaths: false); } _memberConfigurations.Add(expression); memberOptions(expression); return this; } public IMappingExpression<TSource, TDestination> ForMember<TMember>(Expression<Func<TDestination, TMember>> destinationMember, Action<IMemberConfigurationExpression<TSource, TDestination, TMember>> memberOptions) { var memberInfo = ReflectionHelper.FindProperty(destinationMember); return ForDestinationMember(memberInfo, memberOptions); } public IMappingExpression<TSource, TDestination> ForMember(string name, Action<IMemberConfigurationExpression<TSource, TDestination, object>> memberOptions) { var member = DestinationType.GetFieldOrProperty(name); return ForDestinationMember(member, memberOptions); } public void ForAllOtherMembers(Action<IMemberConfigurationExpression<TSource, TDestination, object>> memberOptions) { _allMemberOptions = memberOptions; _memberFilter = m => GetDestinationMemberConfiguration(m) == null; } public void ForAllMembers(Action<IMemberConfigurationExpression<TSource, TDestination, object>> memberOptions) { _allMemberOptions = memberOptions; _memberFilter = _ => true; } public IMappingExpression<TSource, TDestination> IgnoreAllPropertiesWithAnInaccessibleSetter() { foreach(var property in DestinationType.PropertiesWithAnInaccessibleSetter()) { IgnoreDestinationMember(property); } return this; } private void IgnoreDestinationMember(MemberInfo property, bool ignorePaths = true) => ForDestinationMember<object>(property, options => options.Ignore(ignorePaths)); public IMappingExpression<TSource, TDestination> IgnoreAllSourcePropertiesWithAnInaccessibleSetter() { foreach(var property in SourceType.PropertiesWithAnInaccessibleSetter()) { ForSourceMember(property.Name, options => options.Ignore()); } return this; } public IMappingExpression<TSource, TDestination> Include<TOtherSource, TOtherDestination>() where TOtherSource : TSource where TOtherDestination : TDestination => IncludeCore(typeof(TOtherSource), typeof(TOtherDestination)); public IMappingExpression<TSource, TDestination> Include(Type otherSourceType, Type otherDestinationType) { CheckIsDerived(otherSourceType, SourceType); CheckIsDerived(otherDestinationType, DestinationType); return IncludeCore(otherSourceType, otherDestinationType); } IMappingExpression<TSource, TDestination> IncludeCore(Type otherSourceType, Type otherDestinationType) { TypeMapActions.Add(tm => tm.IncludeDerivedTypes(otherSourceType, otherDestinationType)); return this; } public IMappingExpression<TSource, TDestination> IncludeBase<TSourceBase, TDestinationBase>() => IncludeBase(typeof(TSourceBase), typeof(TDestinationBase)); public IMappingExpression<TSource, TDestination> IncludeBase(Type sourceBase, Type destinationBase) { CheckIsDerived(SourceType, sourceBase); CheckIsDerived(DestinationType, destinationBase); TypeMapActions.Add(tm => tm.IncludeBaseTypes(sourceBase, destinationBase)); return this; } public void ProjectUsing(Expression<Func<TSource, TDestination>> projectionExpression) { TypeMapActions.Add(tm => tm.CustomProjection = projectionExpression); } public IMappingExpression<TSource, TDestination> MaxDepth(int depth) { TypeMapActions.Add(tm => tm.MaxDepth = depth); return this; } public IMappingExpression<TSource, TDestination> ConstructUsingServiceLocator() { TypeMapActions.Add(tm => tm.ConstructDestinationUsingServiceLocator = true); return this; } public IMappingExpression<TDestination, TSource> ReverseMap() { _reverseMap = CreateReverseMapExpression(); _reverseMap._memberConfigurations.AddRange(_memberConfigurations.Select(m => m.Reverse()).Where(m=>m!=null)); return _reverseMap; } public IMappingExpression<TSource, TDestination> ForSourceMember(Expression<Func<TSource, object>> sourceMember, Action<ISourceMemberConfigurationExpression> memberOptions) { var memberInfo = ReflectionHelper.FindProperty(sourceMember); var srcConfig = new SourceMappingExpression(memberInfo); memberOptions(srcConfig); _sourceMemberConfigurations.Add(srcConfig); return this; } public IMappingExpression<TSource, TDestination> ForSourceMember(string sourceMemberName, Action<ISourceMemberConfigurationExpression> memberOptions) { var memberInfo = SourceType.GetFieldOrProperty(sourceMemberName); var srcConfig = new SourceMappingExpression(memberInfo); memberOptions(srcConfig); _sourceMemberConfigurations.Add(srcConfig); return this; } public IMappingExpression<TSource, TDestination> Substitute<TSubstitute>(Func<TSource, TSubstitute> substituteFunc) { TypeMapActions.Add(tm => { Expression<Func<TSource, TDestination, ResolutionContext, TSubstitute>> expr = (src, dest, ctxt) => substituteFunc(src); tm.Substitution = expr; }); return this; } public void ConvertUsing(Func<TSource, TDestination> mappingFunction) { TypeMapActions.Add(tm => { Expression<Func<TSource, TDestination, ResolutionContext, TDestination>> expr = (src, dest, ctxt) => mappingFunction(src); tm.CustomMapper = expr; }); } public void ConvertUsing(Func<TSource, TDestination, TDestination> mappingFunction) { TypeMapActions.Add(tm => { Expression<Func<TSource, TDestination, ResolutionContext, TDestination>> expr = (src, dest, ctxt) => mappingFunction(src, dest); tm.CustomMapper = expr; }); } public void ConvertUsing(Func<TSource, TDestination, ResolutionContext, TDestination> mappingFunction) { TypeMapActions.Add(tm => { Expression<Func<TSource, TDestination, ResolutionContext, TDestination>> expr = (src, dest, ctxt) => mappingFunction(src, dest, ctxt); tm.CustomMapper = expr; }); } public void ConvertUsing(ITypeConverter<TSource, TDestination> converter) { ConvertUsing(converter.Convert); } public void ConvertUsing<TTypeConverter>() where TTypeConverter : ITypeConverter<TSource, TDestination> { TypeMapActions.Add(tm => tm.TypeConverterType = typeof (TTypeConverter)); } public IMappingExpression<TSource, TDestination> BeforeMap(Action<TSource, TDestination> beforeFunction) { TypeMapActions.Add(tm => { Expression<Action<TSource, TDestination, ResolutionContext>> expr = (src, dest, ctxt) => beforeFunction(src, dest); tm.AddBeforeMapAction(expr); }); return this; } public IMappingExpression<TSource, TDestination> BeforeMap(Action<TSource, TDestination, ResolutionContext> beforeFunction) { TypeMapActions.Add(tm => { Expression<Action<TSource, TDestination, ResolutionContext>> expr = (src, dest, ctxt) => beforeFunction(src, dest, ctxt); tm.AddBeforeMapAction(expr); }); return this; } public IMappingExpression<TSource, TDestination> BeforeMap<TMappingAction>() where TMappingAction : IMappingAction<TSource, TDestination> { void BeforeFunction(TSource src, TDestination dest, ResolutionContext ctxt) => ((TMappingAction) ctxt.Options.ServiceCtor(typeof(TMappingAction))).Process(src, dest); return BeforeMap(BeforeFunction); } public IMappingExpression<TSource, TDestination> AfterMap(Action<TSource, TDestination> afterFunction) { TypeMapActions.Add(tm => { Expression<Action<TSource, TDestination, ResolutionContext>> expr = (src, dest, ctxt) => afterFunction(src, dest); tm.AddAfterMapAction(expr); }); return this; } public IMappingExpression<TSource, TDestination> AfterMap(Action<TSource, TDestination, ResolutionContext> afterFunction) { TypeMapActions.Add(tm => { Expression<Action<TSource, TDestination, ResolutionContext>> expr = (src, dest, ctxt) => afterFunction(src, dest, ctxt); tm.AddAfterMapAction(expr); }); return this; } public IMappingExpression<TSource, TDestination> AfterMap<TMappingAction>() where TMappingAction : IMappingAction<TSource, TDestination> { void AfterFunction(TSource src, TDestination dest, ResolutionContext ctxt) => ((TMappingAction) ctxt.Options.ServiceCtor(typeof(TMappingAction))).Process(src, dest); return AfterMap(AfterFunction); } public IMappingExpression<TSource, TDestination> ConstructUsing(Func<TSource, TDestination> ctor) { TypeMapActions.Add(tm => { Expression<Func<TSource, ResolutionContext, TDestination>> expr = (src, ctxt) => ctor(src); tm.DestinationCtor = expr; }); return this; } public IMappingExpression<TSource, TDestination> ConstructUsing(Func<TSource, ResolutionContext, TDestination> ctor) { TypeMapActions.Add(tm => { Expression<Func<TSource, ResolutionContext, TDestination>> expr = (src, ctxt) => ctor(src, ctxt); tm.DestinationCtor = expr; }); return this; } public IMappingExpression<TSource, TDestination> ConstructProjectionUsing(Expression<Func<TSource, TDestination>> ctor) { TypeMapActions.Add(tm => { tm.ConstructExpression = ctor; var ctxtParam = Parameter(typeof (ResolutionContext), "ctxt"); var srcParam = Parameter(typeof (TSource), "src"); var body = ctor.ReplaceParameters(srcParam); tm.DestinationCtor = Lambda(body, srcParam, ctxtParam); }); return this; } private IMappingExpression<TSource, TDestination> ForDestinationMember<TMember>(MemberInfo destinationProperty, Action<MemberConfigurationExpression<TSource, TDestination, TMember>> memberOptions) { var expression = (MemberConfigurationExpression<TSource, TDestination, TMember>) CreateMemberConfigurationExpression<TMember>(destinationProperty, SourceType); _memberConfigurations.Add(expression); memberOptions(expression); return this; } public void As<T>() where T : TDestination => As(typeof(T)); public void As(Type typeOverride) { CheckIsDerived(typeOverride, DestinationType); TypeMapActions.Add(tm => tm.DestinationTypeOverride = typeOverride); } private void CheckIsDerived(Type derivedType, Type baseType) { if(!baseType.IsAssignableFrom(derivedType) && !derivedType.IsGenericTypeDefinition() && !baseType.IsGenericTypeDefinition()) { throw new ArgumentOutOfRangeException(nameof(derivedType), $"{derivedType} is not derived from {baseType}."); } } public IMappingExpression<TSource, TDestination> ForCtorParam(string ctorParamName, Action<ICtorParamConfigurationExpression<TSource>> paramOptions) { var ctorParamExpression = new CtorParamConfigurationExpression<TSource>(ctorParamName); paramOptions(ctorParamExpression); _ctorParamConfigurations.Add(ctorParamExpression); return this; } public IMappingExpression<TSource, TDestination> DisableCtorValidation() { TypeMapActions.Add(tm => { tm.DisableConstructorValidation = true; }); return this; } public IMappingExpression<TSource, TDestination> AddTransform<TValue>(Expression<Func<TValue, TValue>> transformer) { var config = new ValueTransformerConfiguration(typeof(TValue), transformer); _valueTransformers.Add(config); return this; } public IMappingExpression<TSource, TDestination> ValidateMemberList(MemberList memberList) { TypeMapActions.Add(tm => { tm.ConfiguredMemberList = memberList; }); return this; } private IPropertyMapConfiguration GetDestinationMemberConfiguration(MemberInfo destinationMember) => _memberConfigurations.FirstOrDefault(m => m.DestinationMember == destinationMember); public void Configure(TypeMap typeMap) { foreach (var destProperty in typeMap.DestinationTypeDetails.PublicWriteAccessors) { var attrs = destProperty.GetCustomAttributes(true); if (attrs.Any(x => x is IgnoreMapAttribute)) { IgnoreDestinationMember(destProperty); var sourceProperty = typeMap.SourceType.GetInheritedMember(destProperty.Name); if (sourceProperty != null) { _reverseMap?.IgnoreDestinationMember(sourceProperty); } } if (typeMap.Profile.GlobalIgnores.Contains(destProperty.Name) && GetDestinationMemberConfiguration(destProperty) == null) { IgnoreDestinationMember(destProperty); } } if (_allMemberOptions != null) { foreach (var accessor in typeMap.DestinationTypeDetails.PublicReadAccessors.Where(_memberFilter)) { ForDestinationMember(accessor, _allMemberOptions); } } foreach (var action in TypeMapActions) { action(typeMap); } foreach (var memberConfig in _memberConfigurations) { memberConfig.Configure(typeMap); } foreach (var memberConfig in _sourceMemberConfigurations) { memberConfig.Configure(typeMap); } foreach (var paramConfig in _ctorParamConfigurations) { paramConfig.Configure(typeMap); } foreach (var valueTransformer in _valueTransformers) { typeMap.AddValueTransformation(valueTransformer); } if (_reverseMap != null) { ReverseSourceMembers(typeMap); foreach(var destProperty in typeMap.GetPropertyMaps().Where(pm => pm.Ignored)) { _reverseMap.ForSourceMember(destProperty.DestinationProperty.Name, opt => opt.Ignore()); } foreach(var includedDerivedType in typeMap.IncludedDerivedTypes) { _reverseMap.Include(includedDerivedType.DestinationType, includedDerivedType.SourceType); } foreach(var includedBaseType in typeMap.IncludedBaseTypes) { _reverseMap.IncludeBase(includedBaseType.DestinationType, includedBaseType.SourceType); } } } private void ReverseSourceMembers(TypeMap typeMap) { foreach(var propertyMap in typeMap.GetPropertyMaps().Where(p => p.SourceMembers.Count > 1 && !p.SourceMembers.Any(s=>s is MethodInfo))) { _reverseMap.TypeMapActions.Add(reverseTypeMap => { var memberPath = new MemberPath(propertyMap.SourceMembers); var newDestination = Parameter(reverseTypeMap.DestinationType, "destination"); var path = propertyMap.SourceMembers.MemberAccesses(newDestination); var forPathLambda = Lambda(path, newDestination); var pathMap = reverseTypeMap.FindOrCreatePathMapFor(forPathLambda, memberPath, reverseTypeMap); var newSource = Parameter(reverseTypeMap.SourceType, "source"); pathMap.SourceExpression = Lambda(MakeMemberAccess(newSource, propertyMap.DestinationProperty), newSource); }); } } } }