Search code examples
c#.netautomapper

AutoMapper - How to add a custom IObjectMapper


I want to achieve something like described here https://stackoverflow.com/a/34956681/6051621 https://dotnetfiddle.net/vWmRiY.

The idea is to unwrap a Wrapper<T>. Since mapping Wrapper<T> to T does not seem to be supported out of the box, one of the possible solutions would be registering a custom IObjectMapper. The problem is that the MapperRegistry is internal now https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/Mappers/MapperRegistry.cs#LL3C29-L3C31. So how can I achieve this? Do I have some better alternative?

Thank you


Solution

  • The easiest approach would be to just define conversion operators:

    public class MyGenericWrapper<T>
    {
        public T Value {get; set;}
        
        public static explicit operator MyGenericWrapper<T>(T p) => new MyGenericWrapper<T>
        {
            Value = p
        };
        
        public static explicit operator T(MyGenericWrapper<T> p) => p.Value;
    }
    

    But if you don't want to, then you can try something like the following:

    var config = new MapperConfiguration(cfg =>
    {
        cfg.Internal().Mappers.Add(new SourceWrapperMapper());
        cfg.Internal().Mappers.Add(new DestinationWrapperMapper());
        cfg.CreateMap<Dto, Entity>();
        cfg.CreateMap<Entity, Dto>();
    });
    
    var mapper = config.CreateMapper();
    var entity = mapper.Map<Entity>(new Dto
    {
        SomeValue = new MyGenericWrapper<int>{Value = 42}
    });
            
    var dto = mapper.Map<Dto>(entity);
    

    And sample mappers to get you started:

    public class SourceWrapperMapper : IObjectMapper
    { 
        public bool IsMatch(TypePair context) => IsWrappedValue(context.SourceType);
    
        public Expression MapExpression(IGlobalConfiguration configuration,
            ProfileMap profileMap,
            MemberMap memberMap,
            Expression sourceExpression,
            Expression destExpression) =>
            Expression.PropertyOrField(sourceExpression, nameof(MyGenericWrapper<int>.Value));
    
        private static bool IsWrappedValue(Type type)
        {
            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(MyGenericWrapper<>);
        }
    }
    
    public class DestinationWrapperMapper : IObjectMapper
    { 
        public bool IsMatch(TypePair context) => IsWrappedValue(context.DestinationType);
    
        public Expression MapExpression(IGlobalConfiguration configuration,
            ProfileMap profileMap,
            MemberMap memberMap,
            Expression sourceExpression,
            Expression destExpression)
        {
            var wrappedType = memberMap.DestinationType.GenericTypeArguments.First();
            return Expression.Call(Mi.MakeGenericMethod(wrappedType), configuration.MapExpression(profileMap,
                new TypePair(memberMap.SourceType, wrappedType),
                sourceExpression,
                memberMap));
        }
    
        public static MyGenericWrapper<T> Wrap<T>(T p) => new MyGenericWrapper<T>
        {
            Value = p
        };
    
        private static MethodInfo Mi =
            typeof(DestinationWrapperMapper).GetMethod(nameof(Wrap), BindingFlags.Public | BindingFlags.Static);
        private static bool IsWrappedValue(Type type)
        {
            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(MyGenericWrapper<>);
        }
    }