AutoMapper: Getting Unmapped members were found with MemberList.Source

Viewed 87

We do a lot of mapping where I work. We use and love AutoMapper.

We would like to validate our mapping profiles. We also often want to ignore some legacy fields. It would be great to use MemberList.Source for this. And it mostly works fine, unless we want some fields to have some sort of special treatment. AutoMapper gets upset during validation if we want to use extension methods on fields or even ValueResolvers.It claims said fields are not mapped.

Is this by design, a bug, am I “holding it wrong” or just missing something obvious? Error message and repro follows.

Both tests errors out with this message

AutoMapper.AutoMapperConfigurationException

# Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
For no matching constructor, add a no-arg ctor, add optional arguments, or map all of the constructor parameters

Source -> Destination (Source member list)
AutomapperRepro.Source -> AutomapperRepro.Destination (Source member list)

Unmapped properties:
FieldB

Code:

using AutoMapper;
using Xunit;

namespace AutomapperRepro;

public class MappingTests
{
    [Fact]
    public void MappingProfile_IsValid()
    {
        var config = new MapperConfiguration(config => config.AddProfile<MappingProfile>());
        config.AssertConfigurationIsValid();
    }

    [Fact]
    public void MappingProfileValueResolver_IsValid()
    {
        var config = new MapperConfiguration(config => config.AddProfile<MappingProfileValueResolver>());
        config.AssertConfigurationIsValid();
    }
}

public class MappingProfile: Profile
{
    public MappingProfile()
    {
        CreateMap<Source, Destination>(MemberList.Source)
            .ForMember(dest => dest.FieldBPadded, opt => opt.MapFrom(s => s.FieldB.PadFieldB()));
    }
}

public class MappingProfileValueResolver : Profile
{
    public MappingProfileValueResolver()
    {
        CreateMap<Source, Destination>(MemberList.Source)
            .ForMember(dest => dest.FieldBPadded, opt => opt.MapFrom(new PaddingResolver(), src => src.FieldB));
    }
}

public static class PaddingExtentions
{
    public static string PadFieldB(this string src)
    {
        return src.PadLeft(10, '0');
    }
}

public class PaddingResolver : IMemberValueResolver<object, object, string, string>
{
    public string Resolve(object source, object destination, string sourceMember, string destinationMember,
        ResolutionContext context)
    {
        return sourceMember.PadFieldB();
    }
}

public record Source
{
    public string FieldA { get; set; } = string.Empty;
    public string FieldB { get; set; } = string.Empty;
}

public record Destination
{
    public string FieldA { get; set; } = string.Empty;
    public string FieldBPadded { get; set; } = string.Empty;
    public string SomeLegacyFieldThatCanBeIgnored { get; set; } = string.Empty;
    public string SomeLegacyFieldThatCanBeIgnored2 { get; set; } = string.Empty;
    public string SomeLegacyFieldThatCanBeIgnored3 { get; set; } = string.Empty;
}

1 Answers

Both suggestions from @LucianBargaoanu are valid Combining MapFrom with a transformer worked great for me:

public class MappingProfileValueTransformer : Profile
{
    public MappingProfileValueTransformer()
    {
        CreateMap<Source, Destination>(MemberList.Source)
            .ForMember(dest => dest.FieldBPadded, opt =>
            {
                opt.MapFrom(s => s.FieldB);
                opt.AddTransform(dest => dest.PadFieldB());
            });
    }
}
Related