AutoMapper / AutoMapper.Extensions.Microsoft.DependencyInjection

MIT License
258 stars 79 forks source link

AddProfile #169

Closed frankhaugen closed 1 year ago

frankhaugen commented 1 year ago

When registering profiles resolvers are ignored, and so mapping fails. When regestering Assemblies it's fine, also when using the AddProfile<>() in standard instantiation it's also fine.

Based on the documentation and the shape of the .AddAutoMapper() -method, this is a bug in the behaviour. I want to avoid adding entire Assemblies as I will get duplicate mappings so I want to add individual profiles. This however isn't possible if one uses resolvers. The profiles work as expected, but the resolver is nowhere to be found in the ServiceCollection/ServiceProvider.

Is there a way I can workaround this?

Test that passes using .AddProfile in traditional instantiation

    [Theory]
    [InlineData("Frank", "Sinatra", "Frank Sinatra")]
    public void AutoMapperIsManuallyInstanciatedSucceedingExample(string firstName, string lastName, string expected)
    {
        var source = new Source { FirstName = firstName, LastName = lastName };
        var sut = new Mapper(new MapperConfiguration(x => x.AddProfile<TestProfile>()));
        var result = sut.Map<Source, Destination>(source);
        result.Should().NotBeNull();
        result.FullName.Should().Be(expected);
    }

Test that fails using .AddProfile in DI

    [Theory]
    [InlineData("Frank", "Sinatra", "Frank Sinatra")]
    public void AutoMapperIsCorrectlyAddedFailingExample(string firstName, string lastName, string expected)
    {
        var source = new Source { FirstName = firstName, LastName = lastName };
        var serviceCollection = new ServiceCollection();
        serviceCollection
            .AddSingleton<IOtherServiceClient, OtherServiceClient>()
            .AddSingleton<SomeServiceWithMapping>()
            .AddAutoMapper(x => x.AddProfile<TestProfile>());
        var serviceProvider = serviceCollection.BuildServiceProvider();
        var sut = serviceProvider.GetService<SomeServiceWithMapping>();
        var result = sut.Map<Source, Destination>(source);
        Logger.LogInformation(serviceCollection.GetServiceDescriptorsAsConsoleTable());
        result.Should().NotBeNull();
        result.FullName.Should().Be(expected);
    }

Test that Passes using Assemblies in DI

    [Theory]
    [InlineData("Frank", "Sinatra", "Frank Sinatra")]
    public void AutoMapperIsCorrectlyAddedSucceedingExample(string firstName, string lastName, string expected)
    {
        var source = new Source { FirstName = firstName, LastName = lastName };
        var serviceCollection = new ServiceCollection();
        serviceCollection
            .AddSingleton<IOtherServiceClient, OtherServiceClient>()
            .AddSingleton<SomeServiceWithMapping>()
            .AddAutoMapper(GetType().Assembly);
        var serviceProvider = serviceCollection.BuildServiceProvider();
        var sut = serviceProvider.GetService<SomeServiceWithMapping>();
        var result = sut.Map<Source, Destination>(source);
        Logger.LogInformation(serviceCollection.GetServiceDescriptorsAsConsoleTable());
        result.Should().NotBeNull();
        result.FullName.Should().Be(expected);
    }

Classes and Interfaces

    public class OtherServiceClient : IOtherServiceClient
    {
        public void Send(int num) { }
    }

    public interface IOtherServiceClient
    {
        public void Send(int num);
    }

    public class SomeServiceWithMapping
    {
        private readonly IOtherServiceClient _otherServiceClient;
        private readonly IMapper _mapper;
        public SomeServiceWithMapping(IOtherServiceClient otherServiceClient, IMapper mapper)
        {
            _otherServiceClient = otherServiceClient;
            _mapper = mapper;
        }
        public bool HasServiceClient() => _otherServiceClient is not null;
        public void SendNum(int num) => _otherServiceClient.Send(num);
        public TDestination Map<TSource, TDestination>(TSource source) => _mapper.Map<TSource, TDestination>(source);
    }

    public class TestProfile : Profile
    {
        public TestProfile()
        {
            CreateMap<Source, Destination>()
                .ForMember(x => x.FullName, opt => opt.MapFrom<FullNameResolver>()) // Fails
                //.ForMember(x => x.FullName, opt => opt.MapFrom(y => $"{y.FirstName} {y.LastName}")) // Passes
                ;
        }
    }
    public class FullNameResolver : IValueResolver<Source, Destination, string>
    {
        public string Resolve(Source source, Destination destination, string destMember, ResolutionContext context)
        {
            return $"{source.FirstName} {source.LastName}";
        }
    }
    public class Source
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    public class Destination
    {
        public string FullName { get; set; }
    }
lbargaoanu commented 1 year ago

I think this is better suited for StackOverflow.