Created
December 22, 2016 16:11
-
-
Save mikeobrien/5b365fee2f2cefdeaef27c3551af946f to your computer and use it in GitHub Desktop.
IoC friendly Automapper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class AutomapperExtensions | |
{ | |
// The following is a friendlier DSL than the one that ships with | |
// Automapper, for the vast majority of the mapping done in Workforce. | |
// | |
// So instead of this: | |
// .ForMember(x => x.Id, x => x.MapFrom(y => y.Guid)) | |
// You can do this: | |
// .From(x => x.Guid).To(x => x.Id) | |
// | |
// Also automatically adds null check conditions for nested | |
// mappings so you don't have to do it manually. | |
public static MapToMemberDsl<TSource, TSourceMember, TDestination> | |
From<TSource, TDestination, TSourceMember>( | |
this IMappingExpression<TSource, TDestination> expression, | |
Expression<Func<TSource, TSourceMember>> sourceMember) | |
where TSource : class where TDestination : class | |
{ | |
return new MapToMemberDsl<TSource, TSourceMember, TDestination>(expression, sourceMember); | |
} | |
public class MapToMemberDsl<TSource, TSourceMember, TDestination> | |
where TSource : class where TDestination : class | |
{ | |
private readonly IMappingExpression<TSource, TDestination> _expression; | |
private readonly Expression<Func<TSource, TSourceMember>> _sourceMember; | |
public MapToMemberDsl(IMappingExpression<TSource, TDestination> expression, | |
Expression<Func<TSource, TSourceMember>> sourceMember) | |
{ | |
_expression = expression; | |
_sourceMember = sourceMember; | |
} | |
public IMappingExpression<TSource, TDestination> To( | |
Expression<Func<TDestination, object>> destinationMember) | |
{ | |
return _expression.AddNullConditions(_sourceMember, destinationMember) | |
.ForMember(destinationMember, x => x.MapFrom(_sourceMember)); | |
} | |
} | |
public static IMappingExpression<TSource, TDestination> | |
AddNullConditions<TSource, TSourceMember, TDestination>( | |
this IMappingExpression<TSource, TDestination> expression, | |
Expression<Func<TSource, TSourceMember>> sourceMember, | |
Expression<Func<TDestination, object>> destinationMember) | |
{ | |
if (!destinationMember.GetMemberType() | |
.IsOptional()) return expression; | |
var nullChecks = sourceMember.BuildNullChecks(); | |
expression.ForMember(destinationMember, m => m | |
.Condition(y => nullChecks(y))); | |
return expression; | |
} | |
public static Func<T, bool> BuildNullChecks<T, TMember>( | |
this Expression<Func<T, TMember>> sourceMember) | |
{ | |
var chain = sourceMember.GetAccessorChain(); | |
if (chain.Count < 2) return x => true; | |
return chain.ExceptLast().Select(x => x.CreateAccessorExpression().Compile()) | |
.Cast<Func<T, object>>() | |
.Select<Func<T, object>, Func<T, bool>>(x => s => x(s) != null) | |
.Aggregate((a, i) => x => a(x) && i(x)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Registry : StructureMap.Configuration.DSL.Registry | |
{ | |
public Registry() | |
{ | |
ForSingletonOf<MapperConfiguration>() | |
.Use<MapperConfiguration>(); | |
ForSingletonOf<IMapper>().Use(x => x | |
.GetInstance<MapperConfiguration>().CreateMapper()); | |
Scan(x => | |
{ | |
x.TheCallingAssembly(); | |
x.AddAllTypesOf<Profile>(); | |
}); | |
} | |
} | |
public class FarkModelMapping : Profile | |
{ | |
protected sealed override void Configure() | |
{ | |
CreateMap<FarkEntity, FarkOutputModel>() | |
.From(m => m.ApprovedBy.Id).To(x => x.ApprovedByEmployeeId) | |
.From(m => m.Currency.Code).To(x => x.CurrencyCode) | |
.From(m => m.Frequency.Code).To(x => x.FrequencyCode); | |
} | |
} | |
public class FarkController : ApiController | |
{ | |
private readonly IMapper _mapper; | |
private readonly MapperConfiguration _mappingConfiguration; | |
public RequisitionsController(IMapper mapper, | |
MapperConfiguration mappingConfiguration) | |
{ | |
_mapper = mapper; | |
_mappingConfiguration = mappingConfiguration; | |
} | |
public List<FarkOutputModel> GetMany() | |
{ | |
return _farks | |
.ProjectTo<FarkOutputModel>(_mappingConfiguration) | |
.ToList(); | |
} | |
public FarkOutputModel Get(Guid id) | |
{ | |
return _mapper.Map<FarkOutputModel>(_farks.Get(id)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class ExpressionExtensions | |
{ | |
public static Type GetMemberType<T, TMember>( | |
this Expression<Func<T, TMember>> member) | |
{ | |
return member.Body.GetMember().GetMemberType(); | |
} | |
private static MemberInfo GetMember(this Expression expression) | |
{ | |
var memberExpression = expression | |
.UnwrapConversionExpression() as MemberExpression; | |
if (memberExpression == null) throw new | |
InvalidOperationException("Must be a member expression."); | |
return memberExpression.Member; | |
} | |
public static Expression UnwrapConversionExpression(this Expression expression) | |
{ | |
var conversionExpression = expression as UnaryExpression; | |
return conversionExpression != null && | |
conversionExpression.NodeType == ExpressionType.Convert ? | |
conversionExpression.Operand : expression; | |
} | |
public static IEnumerable<T> Generate<T>(this T seed, Func<T, T> map) | |
{ | |
var current = seed; | |
do | |
{ | |
yield return current; | |
current = map(current); | |
} while (current != null); | |
} | |
public static IEnumerable<TResult> SelectWithPrevious<TSource, TResult>( | |
this IEnumerable<TSource> source, | |
Func<TResult, TSource, TResult> resultSelector, | |
Action<TResult, TResult> resultModifier = null) | |
{ | |
if (source == null) throw new ArgumentNullException(nameof(source)); | |
if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); | |
using (var e = source.GetEnumerator()) | |
{ | |
var previous = default(TResult); | |
while (e.MoveNext()) | |
{ | |
var result = resultSelector(previous, e.Current); | |
resultModifier?.Invoke(previous, result); | |
yield return result; | |
previous = result; | |
} | |
} | |
} | |
public class AccessorDescriptor | |
{ | |
public List<AccessorDescriptor> Members { get; set; } | |
public MemberInfo Member { get; set; } | |
} | |
public static List<AccessorDescriptor> GetAccessorChain(this LambdaExpression expression, | |
bool includeVariable = false) | |
{ | |
var root = expression.Body.UnwrapConversionExpression(); | |
if (root.NodeType == ExpressionType.Parameter) | |
return Enumerable.Empty<AccessorDescriptor>().ToList(); | |
var current = root as MemberExpression; | |
if (current == null) throw new InvalidOperationException( | |
"Expression does not start with a member accessor, " + | |
$"instead starts with {expression.Body.GetType()}."); | |
return current | |
.Generate(x => x.Expression.UnwrapConversionExpression() as MemberExpression) | |
.TakeWhile(x => x != null && (includeVariable || current | |
.Expression.NodeType != ExpressionType.Constant)) | |
.Reverse() | |
.SelectWithPrevious<MemberExpression, AccessorDescriptor>( | |
(p, i) => new AccessorDescriptor { Member = i.Member }, | |
(p, i) => i.Members = new List<AccessorDescriptor>( | |
p.OrEmpty(x => x.Members)) { i }) | |
.ToList(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class MapperConfiguration : global::AutoMapper.MapperConfiguration | |
{ | |
public MapperConfiguration(IList<Profile> profiles) : | |
base(config => { profiles.ForEach(config.AddProfile); }) { } | |
public static MapperConfiguration Create( | |
params Profile[] profiles) | |
{ | |
return new MapperConfiguration(profiles.ToList()); | |
} | |
// This overload might look a little odd having a fixed | |
// parameter followed by variable parameters of the same | |
// type but this signature prevents a conflict with the | |
// CreateMapper() overload with no parameters. | |
public static IMapper CreateMapper(Profile profile, | |
params Profile[] profiles) | |
{ | |
return new MapperConfiguration(profiles | |
.Concat(profile).ToList()).CreateMapper(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment