45

I need to map an object to another one using AutoMapper. The tricky question is how can I access an instance of the mapper (instance of IMapper) inside of the mapping configuration or inside of a custom type converter?

The code below does not work, however it is an example of what I would like to achieve - please notice the mapper.Map calls and assume that mappings Customer => CustomerDto and Customer => DetailedCustomerDto are defined.

var config = new MapperConfiguration(
    cfg => cfg.CreateMap<Order, OrderDto>()
        .ForMember(dst => dst.Customer, src => src.ResolveUsing(o => {
            return o.Type == 1
                ? mapper.Map<Customer, CustomerDto>(o.Customer)
                : mapper.Map<Customer, DetailedCustomerDto>(o.Customer)
            })
    );

The client part is:

var mapper = config.CreateMapper();
var orderDto = mapper.Map<Order, OrderDto>(order);

The simplified version of objects I want to map is:

public class Order
{
    public int Type { get; set; }
    public Customer Customer { get; set; }
}

public class Customer
{
    public long Id { get; set; }
    public string Name { get; set; }
}

public class OrderDto
{
    public CustomerDto Customer { get; set; }
}

public class CustomerDto
{
    public long Id { get; set; }
}

public class DetailedCustomerDto : CustomerDto
{
    public string Name { get; set; }
}

As you see from the code above, based on the value of Order.Type, the mapper should map the property Order.Customer to different targets. As one target (DetailedCustomerDto) inherits from the other one (CustomerDto) it becomes a bit tricky.

Please notice that usage of the obsolete and deprecated static method Mapper.Map is NOT an option.

starball
  • 20,030
  • 7
  • 43
  • 238
Anton
  • 2,458
  • 2
  • 18
  • 30

3 Answers3

82

As of AutoMapper 8.0 and up The answer below for 5.1.1 still applies, but note that the use of ResolveUsing has been replaced with an overload of MapFrom, but the signature has otherwise remained consistent.

As of AutoMapper 5.1.1

You can get to the mapper using another overload of ResolveUsing with four parameters, fourth of which is ResolutionContext (context.Mapper):

var config = new MapperConfiguration(
    cfg => {
        cfg.CreateMap<Customer, CustomerDto>();
        cfg.CreateMap<Customer, DetailedCustomerDto>();
        cfg.CreateMap<Order, OrderDto>()
             .ForMember(dst => dst.Customer, src => src.ResolveUsing((order, orderDto, i, context) => {
                return order.Type == 1
                ? context.Mapper.Map<Customer, CustomerDto>(order.Customer)
                : context.Mapper.Map<Customer, DetailedCustomerDto>(order.Customer);
        }));
 });

 var orderTypeOne = new Order();
 orderTypeOne.Type = 1;
 orderTypeOne.Customer = new Customer() {
    Id = 1
 };

 var dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeOne);
 Debug.Assert(dto.Customer.GetType() == typeof (CustomerDto));

 var orderTypeTwo = new Order();
 orderTypeTwo.Type = 2;
 orderTypeTwo.Customer = new Customer() {
     Id = 1
 };
 dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeTwo);
 Debug.Assert(dto.Customer.GetType() == typeof (DetailedCustomerDto));

Prior to AutoMapper 5.1.1

You can get to the mapper using another overload of ResolveUsing with two parameters, first of which is ResolutionResult (result.Context.Engine.Mapper):

var config = new MapperConfiguration(
    cfg => {
        cfg.CreateMap<Customer, CustomerDto>();
        cfg.CreateMap<Customer, DetailedCustomerDto>();
        cfg.CreateMap<Order, OrderDto>()
             .ForMember(dst => dst.Customer, src => src.ResolveUsing((result, order) => {
                return order.Type == 1
                ? result.Context.Engine.Mapper.Map<Customer, CustomerDto>(order.Customer)
                : result.Context.Engine.Mapper.Map<Customer, DetailedCustomerDto>(order.Customer);
        }));
 });

 var orderTypeOne = new Order();
 orderTypeOne.Type = 1;
 orderTypeOne.Customer = new Customer() {
    Id = 1
 };

 var dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeOne);
 Debug.Assert(dto.Customer.GetType() == typeof (CustomerDto));

 var orderTypeTwo = new Order();
 orderTypeTwo.Type = 2;
 orderTypeTwo.Customer = new Customer() {
     Id = 1
 };
 dto = config.CreateMapper().Map<Order, OrderDto>(orderTypeTwo);
 Debug.Assert(dto.Customer.GetType() == typeof (DetailedCustomerDto));
Whit Waldo
  • 4,806
  • 4
  • 48
  • 70
Evk
  • 98,527
  • 8
  • 141
  • 191
  • 1
    Great, thank you. This is exactly what I was looking for. What a pity it cannot be found in the official documentation (or at least I was not able to do so). – Anton May 09 '16 at 13:54
  • 2
    Just FYI, as of AutoMapper v5.1.1 the ResolutionContext that contains the instance of the Mapper object is now on the 4th argument of the ResolveUsing(...) Method, and the "Engine" property is gone. So it would be ResolveUsing((src, dest, result, context) => { return context.Mapper.Map<.....>() } – nano2nd Oct 08 '16 at 19:00
  • @nano2nd, thx for pointing this out, I have updated the answer. – Anton Oct 29 '16 at 19:23
16

In addition to Evk's great answer, which helped me, if you need to do a mapping inside a mapping inside a config/profile that requires a custom constructor (i.e. the type has no default constructor), the following will work in v5.2.0:

CreateMap<Models.Job, Models.API.Job>(MemberList.Source);

CreateMap<StaticPagedList<Models.Job>, StaticPagedList<Models.API.Job>>()
                .ConstructUsing((source, context) => new StaticPagedList<Models.API.Job>(
                    context.Mapper.Map<List<Models.Job>, List<Models.API.Job>>(source.ToList()),
                    source.PageNumber,
                    source.PageSize,
                    source.TotalItemCount));

In this example I'm mapping the X.PagedList custom collection type of one object type onto an equivalent collection of another object type. The first parameter to the lamdba expression is your source object, the second is your ResolutionContext from which you can access a mapper instance to map from.

Breeno
  • 3,007
  • 2
  • 31
  • 30
16

I'm using Automapper 9 and the answers above didn't work for me. Then for resolve my problem that is like yours I use .afterMap, like that:

public class AutoMapperOrder : Profile
{
        public AutoMapperOrder()
        {
            CreateMap<Customer, CustomerDto>()
            //...

            CreateMap<Customer, DetailedCustomerDto>()
            //...

            CreateMap<Order, OrderDto>()
                .AfterMap((src, dest, context) => {
                dest.Customer = src.Type == 1
                    ? context.Mapper.Map<Customer, CustomerDto>(src.Customer)
                    : context.Mapper.Map<Customer, DetailedCustomerDto>(src.Customer)
            }
        }
    }
}

I hope to help somebody.

Rodolfo Luna
  • 829
  • 9
  • 19
  • This **did** help a bit on the way. The only thing is that the mapper instance that you get as context isn't of type `IMapper`. It works in your example but I'd need it to be of the same type as gets injected by DI, i.e. `IMapper`. Is there a way to achieve that? – Konrad Viltersten Nov 18 '21 at 19:14