Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>

<PropertyGroup>
<VersionPrefix>8.0.1</VersionPrefix>
<VersionPrefix>8.1.0</VersionPrefix>
<Authors>Jimmy Bogard</Authors>
<LangVersion>latest</LangVersion>
<WarningsAsErrors>true</WarningsAsErrors>
Expand Down
175 changes: 131 additions & 44 deletions src/AutoMapper/ResolutionContext.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using AutoMapper.Execution;

namespace AutoMapper
{
/// <summary>
/// Context information regarding resolution of a destination value
/// </summary>
public class ResolutionContext
public class ResolutionContext : IRuntimeMapper
{
private Dictionary<ContextCacheKey, object> _instanceCache;
private Dictionary<TypePair, int> _typeDepth;
private readonly IRuntimeMapper _inner;

public ResolutionContext(IMappingOperationOptions options, IRuntimeMapper mapper)
{
Options = options;
_inner = mapper;
}

/// <summary>
/// Mapping operation options
/// </summary>
public IMappingOperationOptions Options { get; }

internal object GetDestination(object source, Type destinationType)
{
InstanceCache.TryGetValue(new ContextCacheKey(source, destinationType), out object destination);
return destination;
}
/// <summary>
/// Context items from <see cref="Options"/>
/// </summary>
public IDictionary<string, object> Items => Options.Items;

internal void CacheDestination(object source, Type destinationType, object destination)
{
InstanceCache[new ContextCacheKey(source, destinationType)] = destination;
}
/// <summary>
/// Current mapper
/// </summary>
public IRuntimeMapper Mapper => this;

public IConfigurationProvider ConfigurationProvider => _inner.ConfigurationProvider;

Func<Type, object> IMapper.ServiceCtor => _inner.ServiceCtor;

ResolutionContext IRuntimeMapper.DefaultContext => _inner.DefaultContext;

/// <summary>
/// Instance cache for resolving circular references
Expand All @@ -44,14 +59,6 @@ public Dictionary<ContextCacheKey, object> InstanceCache
}
}

private void CheckDefault()
{
if(IsDefault)
{
throw new InvalidOperationException();
}
}

/// <summary>
/// Instance cache for resolving keeping track of depth
/// </summary>
Expand All @@ -69,6 +76,99 @@ private Dictionary<TypePair, int> TypeDepth
}
}

TDestination IMapper.Map<TDestination>(object source)
=> (TDestination)_inner.Map(source, null, source.GetType(), typeof(TDestination), this);

TDestination IMapper.Map<TDestination>(object source, Action<IMappingOperationOptions> opts)
{
opts(Options);

return ((IMapper)this).Map<TDestination>(source);
}

TDestination IMapper.Map<TSource, TDestination>(TSource source)
=> _inner.Map(source, default(TDestination), this);

TDestination IMapper.Map<TSource, TDestination>(TSource source, Action<IMappingOperationOptions<TSource, TDestination>> opts)
{
var typedOptions = new MappingOperationOptions<TSource, TDestination>(_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, TDestination>(TSource source, TDestination destination)
=> _inner.Map(source, destination, this);

TDestination IMapper.Map<TSource, TDestination>(TSource source, TDestination destination, Action<IMappingOperationOptions<TSource, TDestination>> opts)
{
var typedOptions = new MappingOperationOptions<TSource, TDestination>(_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<IMappingOperationOptions> 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<IMappingOperationOptions> 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, TDestination>(TSource source, TDestination destination, ResolutionContext context,
IMemberMap memberMap)
=> _inner.Map(source, destination, context, memberMap);

IQueryable<TDestination> IMapper.ProjectTo<TDestination>(IQueryable source, object parameters, params Expression<Func<TDestination, object>>[] membersToExpand)
=> _inner.ProjectTo(source, parameters, membersToExpand);

IQueryable<TDestination> IMapper.ProjectTo<TDestination>(IQueryable source, IDictionary<string, object> parameters, params string[] membersToExpand)
=> _inner.ProjectTo<TDestination>(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]++;
Expand All @@ -87,37 +187,24 @@ internal int GetTypeDepth(TypePair types)
return TypeDepth[types];
}

/// <summary>
/// Current mapper
/// </summary>
public IRuntimeMapper Mapper { get; }

/// <summary>
/// Current configuration
/// </summary>
public IConfigurationProvider ConfigurationProvider => Mapper.ConfigurationProvider;

/// <summary>
/// Context items from <see cref="Options"/>
/// </summary>
public IDictionary<string, object> 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, TDestination>(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<ContextCacheKey>
Expand Down
98 changes: 97 additions & 1 deletion src/UnitTests/ContextItems.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace AutoMapper.UnitTests
using System.Collections.Generic;
using System.Linq;

namespace AutoMapper.UnitTests
{
namespace ContextItems
{
Expand Down Expand Up @@ -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<FromCar> FromCars { get; set; }
}

public class ToGarage
{
public List<ToCar> 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<FromGarage, ToGarage>()
.ForMember(dest => dest.ToCars, opts => opts.MapFrom((src, dest, destVal, ctx) =>
{
var toCars = new List<ToCar>();

ToCar toCar;
foreach (var fromCar in src.FromCars)
{
toCar = ctx.Mapper.Map<ToCar>(fromCar);
if (toCar == null)
continue;

toCars.Add(toCar);
}

return toCars;
}));

cfg.CreateMap<FromCar, ToCar>()
.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<FromCar>
{
new FromCar {Door = door, Id = 2, Name = "Volvo"},
new FromCar {Door = door, Id = 3, Name = "Hyundai"},
}
};

var toGarage = Mapper.Map<ToGarage>(fromGarage, opts =>
{
opts.Items.Add("Door", door);
});

foreach (var d in toGarage.ToCars.Select(c => c.Door))
{
d.ShouldBeSameAs(door);
}
}
}
}
}