Call async method during automapper mapping

Viewed 529

I'm trying to learn automapper (version 10.0.0) and I'm setting up my DTO input object to become my Entity Framework model object, so I did this:

CreateMap<RequestInputDTO, Request>()
    .ForMember(x => x.RequestedById, x => x.Ignore())
    .ForMember(x => x.RequestedForId, x => x.Ignore())
    .ForMember(x => x.OrgHierarchyId, x => x.Ignore())
    .AfterMap((src, dst) => {
        dst.Uuid = Guid.NewGuid();
        dst.RequestedUtc = DateTime.UtcNow;
    });

And so far that part is working fine. However, for those three properties I told it to ignore, I have to do database lookups. They send me a name for example, I grab the ID of the corresponding record and then use that for the ID. So when I receive the request I'm doing this:

var request = _mapper.Map<Request>(dto);
request.RequestedById = await WwidToIdAsync(dto.RequestedByWwid);
request.OrgHierarchyId = await OrgHierarchyNumberToIdAsync(dto.RequestedForOrgHierarchyCode);
request.RequestedForId = await WwidToIdAsync(dto.RequestedForWwid);

While that seems to be doing the right thing, I don't like that I've now disconnected those three items from the automapping. It'd be too easy for someone to forget to do it. I can't figure out how to get the three lines handled automatically during the mapping since AfterMap isn't async.

1 Answers

Had the same issue with .NET 6.0. I don't know if you had the issue on Core or prior versions, but here's a solution (or maybe workaround) for AutoMapper 11 and .NET 6.0 (can be applied to any Core version, because it is using dependency injection).

  1. I've added a new service where mentioned async methods are located:
    // Input and output parameter types were defined based on guessing from names :)
    public interface IMyService
    {
        Task<bool> WwidToIdAsync(bool requestedByWwid);
        Task<int> OrgHierarchyNumberToIdAsync(bool requestedForOrgHierarchyCode);
    }    

    public class MyService : IMyService
    {
        public async Task<bool> WwidToIdAsync(bool requestedByWwid)
        {
            // Your db lookup here
        }
    
        public async Task<int> OrgHierarchyNumberToIdAsync(bool 
                     requestedForOrgHierarchyCode)
        {
            // Another db lookup here
        }
    }
  1. Register the service above in dependency container:
    services.AddScoped<IMyService, MyService>();
  1. Here's where all the magic happens: creating new MappingAction and injecting IMyService to it.
public class RequestInputMappingAction : IMappingAction<RequestInputDTO, Request>
{
    private readonly IMyService _myService;

    public RequestInputMappingAction(IMyService myService)
    {
        _myService = myService;
    }

    public void Process(RequestInputDTO source, Request destination, ResolutionContext context)
    {
        destination.Uuid = Guid.NewGuid();
        destination.RequestedUtc = DateTime.UtcNow;
        destination.RequestedById = _myService.WwidToIdAsync(source.RequestedByWwid).GetAwaiter().GetResult();
        destination.OrgHierarchyId = _myService.OrgHierarchyNumberToIdAsync(source.RequestedForOrgHierarchyCode).GetAwaiter().GetResult();
        destination.RequestedForId = _myService.WwidToIdAsync(source.RequestedForWwid).GetAwaiter().GetResult();
    }
}
  1. And the final step - CreateMap
CreateMap<RequestInputDTO, Request>()
            .AfterMap<RequestInputMappingAction>();
Related