Skip to content

Instantly share code, notes, and snippets.

@mikeobrien
Created December 22, 2016 16:11
Show Gist options
  • Save mikeobrien/5b365fee2f2cefdeaef27c3551af946f to your computer and use it in GitHub Desktop.
Save mikeobrien/5b365fee2f2cefdeaef27c3551af946f to your computer and use it in GitHub Desktop.
IoC friendly Automapper
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));
}
}
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));
}
}
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();
}
}
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