Skip to content

Commit 4ec71d0

Browse files
committed
remove ResolutionContext.Options
1 parent 798ebf2 commit 4ec71d0

File tree

6 files changed

+61
-33
lines changed

6 files changed

+61
-33
lines changed

docs/12.0-Upgrade-Guide.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# 12.0 Upgrade Guide
2+
3+
[Release notes](https://github.com/AutoMapper/AutoMapper/releases/tag/v12.0.0).
4+
5+
## Equivalent settings overwrite each other
6+
7+
That applies per map and also per member. For example, you can have only one type converter per map and only one resolver per member.
8+
9+
It might not be obvious that some settings are equivalent. For example, a value converter is a special kind of resolver, so a `ConvertUsing` will overwrite a `MapFrom`
10+
for the same member.
11+
12+
You also cannot have for the same map/member separate configurations for `Map` and `ProjectTo`.
13+
14+
Another possible occurence is with `ForAllMaps` and `ForAllPropertyMaps` when it's possible to overwrite things already set in a particular map.
15+
16+
## `ResolutionContext.Options` was removed
17+
18+
You should use `ResolutionContext.Items` to access the items passed in the `Map` call.
19+
20+
Instead of `ServiceCtor` you should use dependency injection or pass the needed objects in the `Map` call.

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ New to AutoMapper? Check out the :doc:`Getting-started` page first.
7272
:caption: Upgrading
7373

7474
API-Changes
75+
12.0-Upgrade-Guide
7576
11.0-Upgrade-Guide
7677
10.0-Upgrade-Guide
7778
9.0-Upgrade-Guide

src/AutoMapper/ApiCompatBaseline.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMappe
33
InterfacesShouldHaveSameMembers : Interface member 'public TMappingExpression AutoMapper.IMappingExpressionBase<TSource, TDestination, TMappingExpression>.AsProxy()' is present in the implementation but not in the contract.
44
InterfacesShouldHaveSameMembers : Interface member 'public void AutoMapper.IMappingExpressionBase<TSource, TDestination, TMappingExpression>.AsProxy()' is present in the contract but not in the implementation.
55
MembersMustExist : Member 'public void AutoMapper.IMappingExpressionBase<TSource, TDestination, TMappingExpression>.AsProxy()' does not exist in the implementation but it does exist in the contract.
6+
MembersMustExist : Member 'public AutoMapper.IMappingOperationOptions AutoMapper.ResolutionContext.Options.get()' does not exist in the implementation but it does exist in the contract.
67
TypesMustExist : Type 'AutoMapper.ValueResolverConfiguration' does not exist in the implementation but it does exist in the contract.
78
MembersMustExist : Member 'public void AutoMapper.Configuration.MappingExpressionBase<TSource, TDestination, TMappingExpression>.AsProxy()' does not exist in the implementation but it does exist in the contract.
89
CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.IgnoreAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation.
@@ -16,4 +17,4 @@ CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMappe
1617
MembersMustExist : Member 'public System.Linq.IQueryable<TDestination> AutoMapper.QueryableExtensions.Extensions.Map<TSource, TDestination>(System.Linq.IQueryable<TSource>, System.Linq.IQueryable<TDestination>, AutoMapper.IConfigurationProvider)' does not exist in the implementation but it does exist in the contract.
1718
MembersMustExist : Member 'public System.Collections.Generic.IReadOnlyCollection<System.Reflection.MemberInfo> AutoMapper.QueryableExtensions.MemberVisitor.MemberPath.get()' does not exist in the implementation but it does exist in the contract.
1819
TypesMustExist : Type 'AutoMapper.QueryableExtensions.Impl.MemberAccessQueryMapperVisitor' does not exist in the implementation but it does exist in the contract.
19-
Total Issues: 17
20+
Total Issues: 18

src/AutoMapper/Mapper.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace AutoMapper
66
{
77
using QueryableExtensions;
88
using IObjectMappingOperationOptions = IMappingOperationOptions<object, object>;
9+
using Factory = Func<Type, object>;
910
using Internal;
1011
public interface IMapperBase
1112
{
@@ -140,18 +141,22 @@ internal interface IInternalRuntimeMapper : IRuntimeMapper
140141
{
141142
TDestination Map<TSource, TDestination>(TSource source, TDestination destination, ResolutionContext context, Type sourceType = null, Type destinationType = null, MemberMap memberMap = null);
142143
ResolutionContext DefaultContext { get; }
144+
Factory ServiceCtor { get; }
143145
}
144146
public class Mapper : IMapper, IInternalRuntimeMapper
145147
{
146148
private readonly IGlobalConfiguration _configurationProvider;
149+
private readonly Factory _serviceCtor;
147150
public Mapper(IConfigurationProvider configurationProvider) : this(configurationProvider, configurationProvider.Internal().ServiceCtor) { }
148-
public Mapper(IConfigurationProvider configurationProvider, Func<Type, object> serviceCtor)
151+
public Mapper(IConfigurationProvider configurationProvider, Factory serviceCtor)
149152
{
150153
_configurationProvider = (IGlobalConfiguration)configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider));
151-
DefaultContext = new ResolutionContext(new MappingOperationOptions<object, object>(serviceCtor ?? throw new NullReferenceException(nameof(serviceCtor))), this);
154+
_serviceCtor = serviceCtor ?? throw new NullReferenceException(nameof(serviceCtor));
155+
DefaultContext = new(this);
152156
}
153157
internal ResolutionContext DefaultContext { get; }
154158
ResolutionContext IInternalRuntimeMapper.DefaultContext => DefaultContext;
159+
Factory IInternalRuntimeMapper.ServiceCtor => _serviceCtor;
155160
public IConfigurationProvider ConfigurationProvider => _configurationProvider;
156161
public TDestination Map<TDestination>(object source) => Map(source, default(TDestination));
157162
public TDestination Map<TDestination>(object source, Action<IMappingOperationOptions<object, TDestination>> opts) => Map(source, default, opts);
@@ -181,19 +186,19 @@ TDestination IInternalRuntimeMapper.Map<TSource, TDestination>(TSource source, T
181186
private TDestination MapWithOptions<TSource, TDestination>(TSource source, TDestination destination, Action<IMappingOperationOptions<TSource, TDestination>> opts,
182187
Type sourceType = null, Type destinationType = null)
183188
{
184-
var typedOptions = new MappingOperationOptions<TSource, TDestination>(DefaultContext.Options.ServiceCtor);
189+
MappingOperationOptions<TSource, TDestination> typedOptions = new(_serviceCtor);
185190
opts(typedOptions);
186191
typedOptions.BeforeMapAction?.Invoke(source, destination);
187-
destination = MapCore(source, destination, new ResolutionContext(typedOptions, this), sourceType, destinationType);
192+
destination = MapCore(source, destination, new(this, typedOptions), sourceType, destinationType);
188193
typedOptions.AfterMapAction?.Invoke(source, destination);
189194
return destination;
190195
}
191196
private TDestination MapCore<TSource, TDestination>(
192197
TSource source, TDestination destination, ResolutionContext context, Type sourceType = null, Type destinationType = null, MemberMap memberMap = null)
193198
{
194-
var runtimeTypes = new TypePair(source?.GetType() ?? sourceType ?? typeof(TSource), destination?.GetType() ?? destinationType ?? typeof(TDestination));
195-
var requestedTypes = new TypePair(typeof(TSource), typeof(TDestination));
196-
var mapRequest = new MapRequest(requestedTypes, runtimeTypes, memberMap);
199+
TypePair requestedTypes = new(typeof(TSource), typeof(TDestination));
200+
TypePair runtimeTypes = new(source?.GetType() ?? sourceType ?? requestedTypes.SourceType, destination?.GetType() ?? destinationType ?? requestedTypes.DestinationType);
201+
MapRequest mapRequest = new(requestedTypes, runtimeTypes, memberMap);
197202
return _configurationProvider.GetExecutionPlan<TSource, TDestination>(mapRequest)(source, destination, context);
198203
}
199204
}

src/AutoMapper/ResolutionContext.cs

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,24 @@ public class ResolutionContext : IInternalRuntimeMapper
1111
private Dictionary<ContextCacheKey, object> _instanceCache;
1212
private Dictionary<TypePair, int> _typeDepth;
1313
private readonly IInternalRuntimeMapper _mapper;
14-
internal ResolutionContext(IMappingOperationOptions options, IInternalRuntimeMapper mapper)
14+
private readonly IMappingOperationOptions _options;
15+
internal ResolutionContext(IInternalRuntimeMapper mapper, IMappingOperationOptions options = null)
1516
{
16-
Options = options;
1717
_mapper = mapper;
18+
_options = options;
1819
}
19-
internal ResolutionContext(IInternalRuntimeMapper mapper) : this(mapper.DefaultContext.Options, mapper) { }
2020
/// <summary>
21-
/// Mapping operation options
22-
/// </summary>
23-
public IMappingOperationOptions Options { get; }
24-
/// <summary>
25-
/// Context items from <see cref="Options"/>
21+
/// The items passed in the options of the Map call.
2622
/// </summary>
2723
public IDictionary<string, object> Items
2824
{
2925
get
3026
{
31-
CheckDefault();
32-
return Options.Items;
27+
if (_options == null)
28+
{
29+
ThrowInvalidMap();
30+
}
31+
return _options.Items;
3332
}
3433
}
3534
/// <summary>
@@ -45,7 +44,7 @@ public Dictionary<ContextCacheKey, object> InstanceCache
4544
get
4645
{
4746
CheckDefault();
48-
return _instanceCache ??= new Dictionary<ContextCacheKey, object>();
47+
return _instanceCache ??= new();
4948
}
5049
}
5150
/// <summary>
@@ -56,26 +55,26 @@ private Dictionary<TypePair, int> TypeDepth
5655
get
5756
{
5857
CheckDefault();
59-
return _typeDepth ??= new Dictionary<TypePair, int>();
58+
return _typeDepth ??= new();
6059
}
6160
}
6261
TDestination IMapperBase.Map<TDestination>(object source) => ((IMapperBase)this).Map(source, default(TDestination));
6362
TDestination IMapperBase.Map<TSource, TDestination>(TSource source) => _mapper.Map(source, default(TDestination), this);
64-
TDestination IMapperBase.Map<TSource, TDestination>(TSource source, TDestination destination) => _mapper.Map(source, destination, this);
65-
object IMapperBase.Map(object source, Type sourceType, Type destinationType) => _mapper.Map(source, (object)null, this, sourceType, destinationType);
63+
TDestination IMapperBase.Map<TSource, TDestination>(TSource source, TDestination destination) => _mapper.Map(source, destination, this);
64+
object IMapperBase.Map(object source, Type sourceType, Type destinationType) => _mapper.Map(source, (object)null, this, sourceType, destinationType);
6665
object IMapperBase.Map(object source, object destination, Type sourceType, Type destinationType) => _mapper.Map(source, destination, this, sourceType, destinationType);
6766
TDestination IInternalRuntimeMapper.Map<TSource, TDestination>(TSource source, TDestination destination, ResolutionContext context,
6867
Type sourceType, Type destinationType, MemberMap memberMap) => _mapper.Map(source, destination, context, sourceType, destinationType, memberMap);
69-
internal object CreateInstance(Type type) => Options.ServiceCtor(type) ?? throw new AutoMapperMappingException("Cannot create an instance of type " + type);
70-
internal object GetDestination(object source, Type destinationType) =>
71-
source == null ? null : InstanceCache.GetValueOrDefault(new ContextCacheKey(source, destinationType));
68+
internal object CreateInstance(Type type) => ServiceCtor()(type) ?? throw new AutoMapperMappingException("Cannot create an instance of type " + type);
69+
private Func<Type, object> ServiceCtor() => _options?.ServiceCtor ?? _mapper.ServiceCtor;
70+
internal object GetDestination(object source, Type destinationType) => source == null ? null : InstanceCache.GetValueOrDefault(new(source, destinationType));
7271
internal void CacheDestination(object source, Type destinationType, object destination)
7372
{
7473
if (source == null)
7574
{
7675
return;
7776
}
78-
InstanceCache[new ContextCacheKey(source, destinationType)] = destination;
77+
InstanceCache[new(source, destinationType)] = destination;
7978
}
8079
internal void IncrementTypeDepth(TypeMap typeMap) => TypeDepth[typeMap.Types]++;
8180
internal void DecrementTypeDepth(TypeMap typeMap) => TypeDepth[typeMap.Types]--;
@@ -89,24 +88,26 @@ internal bool OverTypeDepth(TypeMap typeMap)
8988
return depth > typeMap.MaxDepth;
9089
}
9190
internal bool IsDefault => this == _mapper.DefaultContext;
91+
Func<Type, object> IInternalRuntimeMapper.ServiceCtor => ServiceCtor();
9292
internal static void CheckContext(ref ResolutionContext resolutionContext)
9393
{
9494
if (resolutionContext.IsDefault)
9595
{
96-
resolutionContext = new ResolutionContext(resolutionContext._mapper);
96+
resolutionContext = new(resolutionContext._mapper);
9797
}
9898
}
99-
internal TDestination MapInternal<TSource, TDestination>(TSource source, TDestination destination, MemberMap memberMap) =>
99+
internal TDestination MapInternal<TSource, TDestination>(TSource source, TDestination destination, MemberMap memberMap) =>
100100
_mapper.Map(source, destination, this, memberMap: memberMap);
101-
internal object Map(object source, object destination, Type sourceType, Type destinationType, MemberMap memberMap) =>
101+
internal object Map(object source, object destination, Type sourceType, Type destinationType, MemberMap memberMap) =>
102102
_mapper.Map(source, destination, this, sourceType, destinationType, memberMap);
103103
private void CheckDefault()
104104
{
105105
if (IsDefault)
106106
{
107-
throw new InvalidOperationException("You must use a Map overload that takes Action<IMappingOperationOptions>!");
107+
ThrowInvalidMap();
108108
}
109109
}
110+
private static void ThrowInvalidMap() => throw new InvalidOperationException("Context.Items are only available when using a Map overload that takes Action<IMappingOperationOptions>!");
110111
}
111112
public readonly struct ContextCacheKey : IEquatable<ContextCacheKey>
112113
{

src/UnitTests/ContextItems.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class ContextResolver : IMemberValueResolver<Source, Dest, int, int>
2525
{
2626
public int Resolve(Source src, Dest d, int source, int dest, ResolutionContext context)
2727
{
28-
return source + (int)context.Options.Items["Item"];
28+
return source + (int)context.Items["Item"];
2929
}
3030
}
3131

@@ -69,7 +69,7 @@ public void Should_report_error()
6969
{
7070
var inner = ex.InnerException;
7171
inner.ShouldBeOfType<InvalidOperationException>();
72-
inner.Message.ShouldBe("You must use a Map overload that takes Action<IMappingOperationOptions>!");
72+
inner.Message.ShouldBe("Context.Items are only available when using a Map overload that takes Action<IMappingOperationOptions>!");
7373
});
7474
}
7575
}
@@ -119,7 +119,7 @@ public void Should_use_value_passed_in()
119119
var config = new MapperConfiguration(cfg =>
120120
{
121121
cfg.CreateMap<Source, Dest>()
122-
.ForMember(d => d.Value1, opt => opt.MapFrom((source, d, dMember, context) => (int)context.Options.Items["Item"] + source.Value1));
122+
.ForMember(d => d.Value1, opt => opt.MapFrom((source, d, dMember, context) => (int)context.Items["Item"] + source.Value1));
123123
});
124124

125125
var dest = config.CreateMapper().Map<Source, Dest>(new Source { Value1 = 5 }, opt => { opt.Items["Item"] = 10; });

0 commit comments

Comments
 (0)