Skip to content

Commit d8e77bd

Browse files
authored
Merge pull request #2933 from AutoMapper/type-map-attributes
Attributes to define a type map configuration
2 parents 89bb8bf + ccab637 commit d8e77bd

15 files changed

+934
-800
lines changed

src/AutoMapper/AutoMapAttribute.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
3+
namespace AutoMapper
4+
{
5+
/// <summary>
6+
/// Auto map to this destination type from the specified source type.
7+
/// Discovered during scanning assembly scanning for configuration when calling <see cref="O:AutoMapper.IMapperConfigurationExpression.AddMaps"/>
8+
/// </summary>
9+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
10+
public sealed class AutoMapAttribute : Attribute
11+
{
12+
public AutoMapAttribute(Type sourceType)
13+
=> SourceType = sourceType;
14+
15+
public Type SourceType { get; }
16+
public bool ReverseMap { get; set; }
17+
18+
public void ApplyConfiguration(IMappingExpression mappingExpression)
19+
{
20+
if (ReverseMap)
21+
{
22+
mappingExpression.ReverseMap();
23+
}
24+
}
25+
}
26+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
6+
namespace AutoMapper.Configuration
7+
{
8+
public class CtorParamConfigurationExpression<TSource> : ICtorParamConfigurationExpression<TSource>, ICtorParameterConfiguration
9+
{
10+
private readonly string _ctorParamName;
11+
private readonly List<Action<ConstructorParameterMap>> _ctorParamActions = new List<Action<ConstructorParameterMap>>();
12+
13+
public CtorParamConfigurationExpression(string ctorParamName) => _ctorParamName = ctorParamName;
14+
15+
public void MapFrom<TMember>(Expression<Func<TSource, TMember>> sourceMember)
16+
{
17+
_ctorParamActions.Add(cpm => cpm.CustomMapExpression = sourceMember);
18+
}
19+
20+
public void MapFrom<TMember>(Func<TSource, ResolutionContext, TMember> resolver)
21+
{
22+
Expression<Func<TSource, ResolutionContext, TMember>> resolverExpression = (src, ctxt) => resolver(src, ctxt);
23+
_ctorParamActions.Add(cpm => cpm.CustomMapFunction = resolverExpression);
24+
}
25+
26+
public void Configure(TypeMap typeMap)
27+
{
28+
var ctorParams = typeMap.ConstructorMap?.CtorParams;
29+
if (ctorParams == null)
30+
{
31+
throw new AutoMapperConfigurationException($"The type {typeMap.Types.DestinationType.Name} does not have a constructor.\n{typeMap.Types.DestinationType.FullName}");
32+
}
33+
34+
var parameter = ctorParams.SingleOrDefault(p => p.Parameter.Name == _ctorParamName);
35+
if (parameter == null)
36+
{
37+
throw new AutoMapperConfigurationException($"{typeMap.Types.DestinationType.Name} does not have a constructor with a parameter named '{_ctorParamName}'.\n{typeMap.Types.DestinationType.FullName}");
38+
}
39+
parameter.CanResolveValue = true;
40+
41+
foreach (var action in _ctorParamActions)
42+
{
43+
action(parameter);
44+
}
45+
}
46+
}
47+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace AutoMapper.Configuration
2+
{
3+
public interface ICtorParameterConfiguration
4+
{
5+
void Configure(TypeMap typeMap);
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace AutoMapper.Configuration
2+
{
3+
public interface ISourceMemberConfiguration
4+
{
5+
void Configure(TypeMap typeMap);
6+
}
7+
}

src/AutoMapper/Configuration/MapperConfigurationExpression.cs

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ public void CreateProfile(string profileName, Action<IProfileExpression> config)
2929

3030
private class NamedProfile : Profile
3131
{
32+
public NamedProfile(string profileName) : base(profileName)
33+
{
34+
}
35+
3236
public NamedProfile(string profileName, Action<IProfileExpression> config) : base(profileName, config)
3337
{
3438
}
@@ -44,41 +48,62 @@ public void AddProfile(Profile profile)
4448
public void AddProfile(Type profileType) => AddProfile((Profile)Activator.CreateInstance(profileType));
4549

4650
public void AddProfiles(IEnumerable<Assembly> assembliesToScan)
47-
=> AddProfilesCore(assembliesToScan);
51+
=> AddMaps(assembliesToScan);
4852

4953
public void AddProfiles(params Assembly[] assembliesToScan)
50-
=> AddProfilesCore(assembliesToScan);
54+
=> AddMaps(assembliesToScan);
5155

5256
public void AddProfiles(IEnumerable<string> assemblyNamesToScan)
53-
=> AddProfilesCore(assemblyNamesToScan.Select(name => Assembly.Load(new AssemblyName(name))));
57+
=> AddMaps(assemblyNamesToScan.Select(name => Assembly.Load(new AssemblyName(name))));
5458

5559
public void AddProfiles(params string[] assemblyNamesToScan)
56-
=> AddProfilesCore(assemblyNamesToScan.Select(name => Assembly.Load(new AssemblyName(name))));
60+
=> AddMaps(assemblyNamesToScan.Select(name => Assembly.Load(new AssemblyName(name))));
5761

5862
public void AddProfiles(IEnumerable<Type> typesFromAssembliesContainingProfiles)
59-
=> AddProfilesCore(typesFromAssembliesContainingProfiles.Select(t => t.GetTypeInfo().Assembly));
63+
=> AddMaps(typesFromAssembliesContainingProfiles.Select(t => t.GetTypeInfo().Assembly));
6064

6165
public void AddProfiles(params Type[] typesFromAssembliesContainingProfiles)
62-
=> AddProfilesCore(typesFromAssembliesContainingProfiles.Select(t => t.GetTypeInfo().Assembly));
66+
=> AddMaps(typesFromAssembliesContainingProfiles.Select(t => t.GetTypeInfo().Assembly));
67+
68+
public void AddMaps(IEnumerable<Assembly> assembliesToScan)
69+
=> AddMapsCore(assembliesToScan);
70+
71+
public void AddMaps(params Assembly[] assembliesToScan)
72+
=> AddMapsCore(assembliesToScan);
73+
74+
public void AddMaps(IEnumerable<string> assemblyNamesToScan)
75+
=> AddMapsCore(assemblyNamesToScan.Select(name => Assembly.Load(new AssemblyName(name))));
6376

64-
private void AddProfilesCore(IEnumerable<Assembly> assembliesToScan)
77+
public void AddMaps(params string[] assemblyNamesToScan)
78+
=> AddMapsCore(assemblyNamesToScan.Select(name => Assembly.Load(new AssemblyName(name))));
79+
80+
public void AddMaps(IEnumerable<Type> typesFromAssembliesContainingMappingDefinitions)
81+
=> AddMapsCore(typesFromAssembliesContainingMappingDefinitions.Select(t => t.GetTypeInfo().Assembly));
82+
83+
public void AddMaps(params Type[] typesFromAssembliesContainingMappingDefinitions)
84+
=> AddMapsCore(typesFromAssembliesContainingMappingDefinitions.Select(t => t.GetTypeInfo().Assembly));
85+
86+
private void AddMapsCore(IEnumerable<Assembly> assembliesToScan)
6587
{
6688
var allTypes = assembliesToScan.Where(a => !a.IsDynamic).SelectMany(a => a.GetDefinedTypes()).ToArray();
89+
var autoMapAttributeProfile = new NamedProfile(nameof(AutoMapAttribute));
6790

68-
var profiles =
69-
allTypes
70-
.Where(t => typeof(Profile).GetTypeInfo().IsAssignableFrom(t))
71-
.Where(t => !t.IsAbstract)
72-
.Select(t => t.AsType());
73-
74-
foreach (var profile in profiles)
91+
foreach (var type in allTypes)
7592
{
76-
AddProfile(profile);
93+
if (typeof(Profile).IsAssignableFrom(type) && !type.IsAbstract)
94+
{
95+
AddProfile(type.AsType());
96+
}
97+
foreach (var autoMapAttribute in type.GetCustomAttributes<AutoMapAttribute>())
98+
{
99+
var mappingExpression = autoMapAttributeProfile.CreateMap(autoMapAttribute.SourceType, type);
100+
autoMapAttribute.ApplyConfiguration(mappingExpression);
101+
}
77102
}
78103

104+
AddProfile(autoMapAttributeProfile);
79105
}
80106

81-
82107
public void ConstructServicesUsing(Func<Type, object> constructor) => ServiceCtor = constructor;
83108
}
84109
}

0 commit comments

Comments
 (0)