I originally posted this post on my blog.
Oh boy! AutoMapper once again.
Today I have CreateMovieRequest
with a boolean property ICanWatchItWithKids
that I want to map to a MPA rating. You know G, PG, PG-13...those ones.
If I can watch it with kids, the property MPARating
on the destination type should get "General." Anything else gets "Restricted."
To my surprise, this test fails:
using AutoMapper;
namespace TestProject1;
[TestClass]
public class WhyAutoMapperWhy
{
public class CreateMovieRequest
{
public string Name { get; set; }
public bool ICanWatchItWithKids { get; set; }
}
public class Movie
{
public string Name { get; set;}
public MPARating Rating { get; set;}
}
public enum MPARating
{
// Sure, there are more.
// But these two are enough.
General,
Restricted
}
[TestMethod]
public void AutoMapperConfig_IsValid()
{
var mapperConfig = new MapperConfiguration(options =>
{
options.CreateMap<CreateMovieRequest, Movie>(MemberList.Source)
.ForMember(
dest => dest.Rating,
opt => opt.MapFrom(src => src.ICanWatchItWithKids
? MPARating.General
: MPARating.Restricted));
});
mapperConfig.AssertConfigurationIsValid();
// 👆👆👆
// AutoMapper.AutoMapperConfigurationException:
// CreateMovieRequest -> Movie (Source member list)
// TestProject1.WhyAutoMapperWhy+CreateMovieRequest -> TestProject1.WhyAutoMapperWhy+Movie (Source member list)
//
// Unmapped properties:
// ICanWatchItWithKids
}
}
It turns out that starting from AutoMapper version 10.0, only source members expressions are considered when validating mappings. And it's buried in the Upgrade Guide here. Arrggg!
Two solutions: One for the lazy and the right one
Since I'm validating mappings based on the source type, I can simply ignore it:
options.CreateMap<CreateMovieRequest, Movie>(MemberList.Source)
.ForMember(
dest => dest.Rating,
opt => opt.MapFrom(src => src.ICanWatchItWithKids ? MPARating.General : MPARating.Restricted))
.ForSourceMember(src => src.ICanWatchItWithKids, opt => opt.DoNotValidate());
// 👆👆👆
// Thanks AutoMapper, I'll take it from here.
It feels like cheating, but it works.
Or, I can use a converter:
options.CreateMap<CreateMovieRequest, Movie>(MemberList.Source)
.ForMember(
dest => dest.Rating,
opt => opt.ConvertUsing( // 👈
new FromBoolToMPARating(), // 👈
src => src.ICanWatchItWithKids));
// And here's the converter: 👇
public class FromBoolToMPARating : IValueConverter<bool, MPARating>
{
public MPARating Convert(bool sourceMember, ResolutionContext context)
{
// Here's the actual mapping: 👇
return sourceMember ? MPARating.General : MPARating.Restricted;
}
}
Another day working with AutoMapper. It would have been way easier mapping that by hand.
Join my email list and get a short, 2-minute email with 4 curated links about programming and software engineering delivered to your inbox every Friday.
Top comments (0)