I'm having some issues mapping a container class that contains an abstract property to my View Model destination class.
//Container class
public class GiftcardDetailResponse : Response
{
//Instance of either UserGiftcardDTO or ImportedGiftcardDTO
public GiftcardInstanceDTO UserGiftcard { get; set; }
}
public abstract class GiftcardInstanceDTO : BaseDTO
{
public int UserId { get; set; }
public decimal Balance { get; set; }
public string BarcodeValue { get; set; }
public string BarcodeUrl { get; set; }
public string Code { get; set; }
public string Pin { get; set; }
public bool RefreshBalanceSupported { get; set; }
public bool Viewed { get; set; }
public bool IsArchived { get; set; }
public virtual UserDTO User { get; set; }
}
public class UserGiftcardDTO : GiftcardInstanceDTO
{
public int GiftcardId { get; set; }
public DateTimeOffset? ActivatedAt { get; set; }
public DateTimeOffset? BalanceUpdatedAt { get; set; }
public string ClaimUrl { get; set; }
public string ClaimSecret { get; set; }
public PrivacySettings Privacy { get; set; }
public bool IsPending { get; set; }
public bool BoughtAsGift { get; set; }
public virtual GiftcardDTO Giftcard { get; set; }
}
public class ImportedGiftcardDTO : GiftcardInstanceDTO
{
public string RetailerName { get; set; }
public string FrontImage { get; set; }
public string BackImage { get; set; }
}
//Front-end view model
public class GiftcardDetailViewModel
{
public int Id { get; set; }
public string RetailerName { get; set; }
public decimal Amount { get; set; }
public string Image { get; set; }
}
CreateMap<GiftcardDetailResponse, GiftcardDetailViewModel>()
.IncludeMembers(src => src.UserGiftcard);
//Automapper should pick a concrete mapping for this
CreateMap<GiftcardInstanceDTO, GiftcardDetailViewModel>()
.IncludeAllDerived();
//Concrete mappings to View Model
CreateMap<UserGiftcardDTO, GiftcardDetailViewModel>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Image, opt => opt.MapFrom(src => src.Giftcard.Image))
.ForMember(dest => dest.Amount, opt => opt.MapFrom(src => src.Balance))
.ForMember(dest => dest.RetailerName, opt => opt.MapFrom(src => src.Giftcard.Merchant.Name));
CreateMap<ImportedGiftcardDTO, GiftcardDetailViewModel>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dest => dest.Image, opt => opt.MapFrom(src => src.FrontImage))
.ForMember(dest => dest.Amount, opt => opt.MapFrom(src => src.Balance))
.ForMember(dest => dest.RetailerName, opt => opt.MapFrom(src => src.RetailerName));
The problem is that Automapper isn't picking my explicit mappings for the derived class of my abstract property when I map GiftcardDetailResponse
to GiftcardDetailViewModel
. For example, if my code looked this
var containerClass = new GiftcardDetailResponse();
containerClass.UserGiftcard = new ImportedGiftcardDTO();
var viewModel = MapperWrapper.Mapper.Map<GiftcardDetailViewModel>(containerClass);
The execution tree looks like this
//Automapper generated execution plan
(src, dest, ctxt) =>
{
GiftcardDetailViewModel typeMapDestination;
return (src == null)
? null
: {
typeMapDestination = dest ?? new GiftcardDetailViewModel();
try
{
var resolvedValue = ((src == null) || ((src.UserGiftcard == null) || false)) ? default(int) : src.UserGiftcard.Id;
typeMapDestination.Id = resolvedValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return default(int);
}
return typeMapDestination;
};
}
It only seems to pick only the properties that share the same name in both GiftcardInstanceDTO
and GiftcardDetailViewModel
instead of using my defined mappings.
But what I'm looking for would be something similar to the execution tree when I only explicitly map my abstract property to my view model such as
var propertyModel = MapperWrapper.Mapper.Map<GiftcardDetailViewModel>(containerClass.UserGiftcard);
which correctly shows me my derived mapping
//Automapper generated execution plan
(src, dest, ctxt) =>
{
GiftcardDetailViewModel typeMapDestination;
return (src == null)
? null
: {
typeMapDestination = dest ?? new GiftcardDetailViewModel();
try
{
var resolvedValue =
{
try
{
return ((src == null) || false) ? default(int) : src.Id;
}
catch (NullReferenceException)
{
return default(int);
}
catch (ArgumentNullException)
{
return default(int);
}
};
typeMapDestination.Id = resolvedValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return default(int);
}
try
{
var resolvedValue =
{
try
{
return ((src == null) || false) ? null : src.RetailerName;
}
catch (NullReferenceException)
{
return null;
}
catch (ArgumentNullException)
{
return null;
}
};
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.RetailerName = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
try
{
var resolvedValue =
{
try
{
return ((src == null) || false) ? null : src.FrontImage;
}
catch (NullReferenceException)
{
return null;
}
catch (ArgumentNullException)
{
return null;
}
};
var propertyValue = (resolvedValue == null) ? null : resolvedValue;
typeMapDestination.Image = propertyValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return null;
}
try
{
var resolvedValue =
{
try
{
return ((src == null) || false) ? default(decimal) : src.Balance;
}
catch (NullReferenceException)
{
return default(decimal);
}
catch (ArgumentNullException)
{
return default(decimal);
}
};
typeMapDestination.Amount = resolvedValue;
}
catch (Exception ex)
{
throw new AutoMapperMappingException(
"Error mapping types.",
ex,
AutoMapper.TypePair,
TypeMap,
PropertyMap);
return default(decimal);
}
return typeMapDestination;
};
}
The documentation says I can use IncludeMembers
to flatten child objects to the destination object when theres already defined mappings. But this behavior doesn't seem to be working correctly in this instance when the child object is abstract.
IncludeMembers
does not implement this "dynamic" behavior, but you can do smth like this:
CreateMap<Source, Destination>().AfterMap((source, destination, context) => context.Mapper.Map(source.InnerSource, destination));