0

TL;DR: Is there a way to create a sort of convention or prefix rule that would use the source or destination class names as prefix for some field mappings using AutoMapper 12.0?

I'm experimenting with AutoMapper 12.0 in a dotnet core 6 project and the entity classes use the entity name as prefix for their keys (eg.: Person class has a PersonId property, which is mapped to the primary key in DB), but the API side has DTOs that don't include those prefixes:

// Entity definition
public class Person
{
    public int PersonId { get; set; }
    public string Name { get; set; }
    // ...
}

// API response definition
public class PersonDetails
{
    public int Id { get; set; }
    public string Name { get; set; }
    // ...
}

The way I configured my mappings to make it work was to manually define the mapping for the IDs using .ForMember(...), but that requires me to do that for every single entity and DTO combination:

public class EntityToViewModelMappingProfile : Profile
{
    public EntityToViewModelMappingProfile() : base(nameof(EntityToViewModelMappingProfile))
    {
        CreateMap<Person, PersonDetails>()
            .ForMember(p => p.Id, o => o.MapFrom(p => p.PersonId))
            .ReverseMap();
        // ...
        // the same code for all of my entities :(
    }
}

I'm looking for a way to avoid a bunch of boilerplate code and have a generic rule that would work for all of my entities without having to write custom mappings for all of them.

Even though I have custom mappings for other fields, that case is the one that seems to be a fit for a custom convention, but I couldn't find a way to implement one that would give me access to the destination and source type names to be able to use them as prefixes.

As Lucian Bargaoanu pointed out in the comments, there is a similar question for an earlier version of AutoMapper here and it suggests using:

        Mapper.Initialize(exp =>
        {
            exp.CreateMap<User, UserDto>();
            exp.ForAllMaps((typeMap, mappingExpression) =>
                mappingExpression.ForMember("Id", o=>o.MapFrom(typeMap.SourceType.Name + "Id"))
            );
        });

AutoMapper 12.0 seems to not support ForAllMaps, from what I researched in their docs. Is there a way to achieve the same with 12.0?

Ricardo Souza
  • 16,030
  • 6
  • 37
  • 69
  • https://stackoverflow.com/questions/47527543/make-automapper-automatically-map-prefixed-properties/47532825#47532825 – Lucian Bargaoanu Nov 10 '22 at 08:30
  • Thanks @LucianBargaoanu, but I'm on AutoMapper 12.0, which doesn't seem to support `ForAllMaps`. That is essentially the same question, but for an earlier version of AutoMapper. – Ricardo Souza Nov 10 '22 at 16:19
  • Maybe do a little research? Or ask a new question. – Lucian Bargaoanu Nov 10 '22 at 16:32
  • Believe me, I spent quite some time researching and trying things on my own before posting this question. I stated the AutoMapper version at the start and on the tags to make it clear it is a newer version, so previous solutions don't necessarily apply. (I will include it in the title as well, just in case). – Ricardo Souza Nov 10 '22 at 16:36
  • Of course it applies. – Lucian Bargaoanu Nov 10 '22 at 16:38

1 Answers1

1

Lucian Bargaoanu's comments lead me to a similar question for a previous version of AutoMapper, but the current version (12.0) doesn't expose that API through the regular public interface anymore. At least it gave me a hint on possible ways of achieving my problem, so I started looking for how to apply the same on newer versions.

By digging through the upgrade docs from past versions, I found that ForAllMaps was moved to the Internal API on version 11.0 and now requires importing AutoMapper.Internal namespace to have access to the .Internal() extension methods on config objects to access that method.

Here is my solution for the problem in AutoMapper 12.0 (possibly compatible with 11.0 as well):

using AutoMapper;
using AutoMapper.Internal; // <== this is important

namespace MyOwn.Solution.AutoMapper
{
    public class DomainToAPIModelMappingProfile : Profile
    {
        public DomainToAPIModelMappingProfile() : base(nameof(DomainToAPIModelMappingProfile))
        {
            CreateMap<Domain.Entities.Person, APIModels.PersonDetails>();

            CreateMap<Domain.Entities.Foo, APIModels.FooData>();
            CreateMap<Domain.Entities.Bar, APIModels.BarData>();
            // ...

            // this is the solution
            this.Internal().ForAllMaps((typeMap, mappingExpression) =>
                mappingExpression.ForMember("Id", o => o.MapFrom(typeMap.SourceType.Name + "Id"))
            );
        }
    }
}
Ricardo Souza
  • 16,030
  • 6
  • 37
  • 69