TIL: AutoMapper Only Considers Simple Mappings When Validating Configurations

Oh boy! AutoMapper once again.

Today I have CreateMovieRequest with a boolean property ICanWatchItWithKids that I want to map to a MPA rating. If I can watch it with kids, the property MPARating on the destination type should get a “General” rating. 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.

For more adventures with AutoMapper, check How to ignore unmapped fields in the destination type.