diff --git a/Directory.Build.props b/Directory.Build.props index 609c44dea8..ca71a5fe41 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 8.0.1 + 8.1.0 Jimmy Bogard latest true diff --git a/src/AutoMapper/ResolutionContext.cs b/src/AutoMapper/ResolutionContext.cs index f04958ae9f..41ed15f7c1 100644 --- a/src/AutoMapper/ResolutionContext.cs +++ b/src/AutoMapper/ResolutionContext.cs @@ -1,31 +1,46 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using AutoMapper.Execution; namespace AutoMapper { /// /// Context information regarding resolution of a destination value /// - public class ResolutionContext + public class ResolutionContext : IRuntimeMapper { private Dictionary _instanceCache; private Dictionary _typeDepth; + private readonly IRuntimeMapper _inner; + + public ResolutionContext(IMappingOperationOptions options, IRuntimeMapper mapper) + { + Options = options; + _inner = mapper; + } /// /// Mapping operation options /// public IMappingOperationOptions Options { get; } - internal object GetDestination(object source, Type destinationType) - { - InstanceCache.TryGetValue(new ContextCacheKey(source, destinationType), out object destination); - return destination; - } + /// + /// Context items from + /// + public IDictionary Items => Options.Items; - internal void CacheDestination(object source, Type destinationType, object destination) - { - InstanceCache[new ContextCacheKey(source, destinationType)] = destination; - } + /// + /// Current mapper + /// + public IRuntimeMapper Mapper => this; + + public IConfigurationProvider ConfigurationProvider => _inner.ConfigurationProvider; + + Func IMapper.ServiceCtor => _inner.ServiceCtor; + + ResolutionContext IRuntimeMapper.DefaultContext => _inner.DefaultContext; /// /// Instance cache for resolving circular references @@ -44,14 +59,6 @@ public Dictionary InstanceCache } } - private void CheckDefault() - { - if(IsDefault) - { - throw new InvalidOperationException(); - } - } - /// /// Instance cache for resolving keeping track of depth /// @@ -69,6 +76,99 @@ private Dictionary TypeDepth } } + TDestination IMapper.Map(object source) + => (TDestination)_inner.Map(source, null, source.GetType(), typeof(TDestination), this); + + TDestination IMapper.Map(object source, Action opts) + { + opts(Options); + + return ((IMapper)this).Map(source); + } + + TDestination IMapper.Map(TSource source) + => _inner.Map(source, default(TDestination), this); + + TDestination IMapper.Map(TSource source, Action> opts) + { + var typedOptions = new MappingOperationOptions(_inner.ServiceCtor); + + opts(typedOptions); + + var destination = default(TDestination); + + typedOptions.BeforeMapAction(source, destination); + + destination = _inner.Map(source, destination, this); + + typedOptions.AfterMapAction(source, destination); + + return destination; + } + + TDestination IMapper.Map(TSource source, TDestination destination) + => _inner.Map(source, destination, this); + + TDestination IMapper.Map(TSource source, TDestination destination, Action> opts) + { + var typedOptions = new MappingOperationOptions(_inner.ServiceCtor); + + opts(typedOptions); + + typedOptions.BeforeMapAction(source, destination); + + destination = _inner.Map(source, destination, this); + + typedOptions.AfterMapAction(source, destination); + + return destination; + } + + object IMapper.Map(object source, Type sourceType, Type destinationType) + => _inner.Map(source, null, sourceType, destinationType, this); + + object IMapper.Map(object source, Type sourceType, Type destinationType, Action opts) + { + opts(Options); + + return ((IMapper)this).Map(source, sourceType, destinationType); + } + + object IMapper.Map(object source, object destination, Type sourceType, Type destinationType) + => _inner.Map(source, destination, sourceType, destinationType, this); + + object IMapper.Map(object source, object destination, Type sourceType, Type destinationType, Action opts) + { + opts(Options); + + return ((IMapper)this).Map(source, destination, sourceType, destinationType); + } + + object IRuntimeMapper.Map(object source, object destination, Type sourceType, Type destinationType, ResolutionContext context, + IMemberMap memberMap) + => _inner.Map(source, destination, sourceType, destinationType, context, memberMap); + + TDestination IRuntimeMapper.Map(TSource source, TDestination destination, ResolutionContext context, + IMemberMap memberMap) + => _inner.Map(source, destination, context, memberMap); + + IQueryable IMapper.ProjectTo(IQueryable source, object parameters, params Expression>[] membersToExpand) + => _inner.ProjectTo(source, parameters, membersToExpand); + + IQueryable IMapper.ProjectTo(IQueryable source, IDictionary parameters, params string[] membersToExpand) + => _inner.ProjectTo(source, parameters, membersToExpand); + + internal object GetDestination(object source, Type destinationType) + { + InstanceCache.TryGetValue(new ContextCacheKey(source, destinationType), out object destination); + return destination; + } + + internal void CacheDestination(object source, Type destinationType, object destination) + { + InstanceCache[new ContextCacheKey(source, destinationType)] = destination; + } + internal void IncrementTypeDepth(TypePair types) { TypeDepth[types]++; @@ -87,37 +187,24 @@ internal int GetTypeDepth(TypePair types) return TypeDepth[types]; } - /// - /// Current mapper - /// - public IRuntimeMapper Mapper { get; } - - /// - /// Current configuration - /// - public IConfigurationProvider ConfigurationProvider => Mapper.ConfigurationProvider; - - /// - /// Context items from - /// - public IDictionary Items => Options.Items; - - public ResolutionContext(IMappingOperationOptions options, IRuntimeMapper mapper) - { - Options = options; - Mapper = mapper; - } + internal void ValidateMap(TypeMap typeMap) + => ConfigurationProvider.AssertConfigurationIsValid(typeMap); - internal bool IsDefault => this == Mapper.DefaultContext; + internal bool IsDefault => this == _inner.DefaultContext; internal TDestination Map(TSource source, TDestination destination, IMemberMap memberMap) - => Mapper.Map(source, destination, this, memberMap); + => _inner.Map(source, destination, this, memberMap); - internal object Map(object source, object destination, Type sourceType, Type destinationType, IMemberMap memberMap) - => Mapper.Map(source, destination, sourceType, destinationType, this, memberMap); + internal object Map(object source, object destination, Type sourceType, Type destinationType, IMemberMap memberMap) + => _inner.Map(source, destination, sourceType, destinationType, this, memberMap); - internal void ValidateMap(TypeMap typeMap) - => ConfigurationProvider.AssertConfigurationIsValid(typeMap); + private void CheckDefault() + { + if (IsDefault) + { + throw new InvalidOperationException(); + } + } } public struct ContextCacheKey : IEquatable diff --git a/src/UnitTests/ContextItems.cs b/src/UnitTests/ContextItems.cs index 05310ad7cb..439896062b 100644 --- a/src/UnitTests/ContextItems.cs +++ b/src/UnitTests/ContextItems.cs @@ -1,4 +1,7 @@ -namespace AutoMapper.UnitTests +using System.Collections.Generic; +using System.Linq; + +namespace AutoMapper.UnitTests { namespace ContextItems { @@ -93,5 +96,98 @@ public void Should_use_value_passed_in() dest.Value1.ShouldBe(15); } } + + public class When_mapping_nested_context_items : AutoMapperSpecBase + { + public class Door { } + + public class FromGarage + { + public List FromCars { get; set; } + } + + public class ToGarage + { + public List ToCars { get; set; } + } + + public class FromCar + { + public int Id { get; set; } + public string Name { get; set; } + public Door Door { get; set; } + } + + public class ToCar + { + public int Id { get; set; } + public string Name { get; set; } + public Door Door { get; set; } + } + + protected override MapperConfiguration Configuration { get; } = new MapperConfiguration(cfg => + { + cfg.CreateMap() + .ForMember(dest => dest.ToCars, opts => opts.MapFrom((src, dest, destVal, ctx) => + { + var toCars = new List(); + + ToCar toCar; + foreach (var fromCar in src.FromCars) + { + toCar = ctx.Mapper.Map(fromCar); + if (toCar == null) + continue; + + toCars.Add(toCar); + } + + return toCars; + })); + + cfg.CreateMap() + .ConvertUsing((src, dest, ctx) => + { + ToCar toCar = null; + FromCar fromCar = src; + + if (fromCar.Name != null) + { + toCar = new ToCar + { + Id = fromCar.Id, + Name = fromCar.Name, + Door = (Door) ctx.Items["Door"] + }; + } + + return toCar; + }); + }); + + [Fact] + public void Should_flow_context_items_to_nested_mappings() + { + var door = new Door(); + var fromGarage = new FromGarage + { + FromCars = new List + { + new FromCar {Door = door, Id = 2, Name = "Volvo"}, + new FromCar {Door = door, Id = 3, Name = "Hyundai"}, + } + }; + + var toGarage = Mapper.Map(fromGarage, opts => + { + opts.Items.Add("Door", door); + }); + + foreach (var d in toGarage.ToCars.Select(c => c.Door)) + { + d.ShouldBeSameAs(door); + } + } + } } } \ No newline at end of file