AutoMapper ProjectTo<>() not finding map

Appsum Solutions picture Appsum Solutions · May 19, 2016 · Viewed 13k times · Source

I have a ASP.NET 5 (running on 4.6.2, not Core) application. I wanted to use the ProjectTo<>() method of AutoMapper to project the results from the database to my viewmodels.

I've tried alot of tests, but it seems that the map solely cannot be found when using the ProjectTo<>(). Using mapper.Map<>() on different locations with the same model and viewmodel perfectly works.

I guess there is something wrong with how AutoMapper works with my DI (Autofac), but I can't figure out what.

Anyway, the code:

Startup.Cs

 public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            (...)

            // Autofac DI
            AutofacContainer = AutofacLoader.Configure(services).Build();

            return AutofacContainer.Resolve<IServiceProvider>();
        }

AutofacLoader.cs

public static ContainerBuilder Configure(IServiceCollection services)
        {
            var builder = new ContainerBuilder();

(...)


            // AutoMapper
            builder.RegisterModule<AutoMapperModule>();

            if (services != null)
            { 
                builder.Populate(services);

            }
            return builder;
        }

AutoMapperModule.cs

public class AutoMapperModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        var mapping = new MapperConfiguration(cfg =>
        {
            cfg.AddProfile(new Core.Mappings.AutoMapperProfileConfiguration());
            cfg.AddProfile(new Dieet.Core.Mappings.AutoMapperProfileConfiguration());
        });
        builder.RegisterInstance(mapping.CreateMapper()).As<IMapper>().AutoActivate();
    }
}

The test that fails with 'Missing map from Patient to PatientViewModel. Create using Mapper.CreateMap'.

   [Fact]
    public async void InfohosServiceReturnsPatientViewModels()
    {
        var db = _container.Resolve<IInfohosDb>();

        var search = new PaginatedSearchBase();
        search.OrderBy = "Naam";

        var mapper = _container.Resolve<IMapper>();

        var result = await search.PagedResultAsAsync<Patient,PatientViewModel >(null,db.Patienten,mapper);
    }

PaginatedSearchBase

public class PaginatedSearchBase
{
    public string OrderBy { get; set; }
    public bool OrderDescending { get; set; }
    public int Page { get; set; } = 1;
    public int PageSize { get; set; } = 10;
}

And finally the extension that calls the ProjectTo

public static class PagedResultExtensions
{
    public static async Task<PagedResult<T>> PagedResultAsync<T>(this PaginatedSearchBase vm, ICollection<Expression<Func<T, bool>>> whereCollection, IEnumerable<T> context) where T : class
    {
        int totalCount;
        var query = PrepareQuery(vm, whereCollection, context, out totalCount);

        return new PagedResult<T>
        {
            Results = await query.ToListAsync(),
            Page = vm.Page,
            PageSize = vm.PageSize,
            Total = totalCount
        };
    }
    public static async Task<PagedResult<TAs>> PagedResultAsAsync<T, TAs>(this PaginatedSearchBase vm, ICollection<Expression<Func<T, bool>>> whereCollection, IEnumerable<T> context, IMapper mapper) where T : class
    {
        int totalCount;
        var query = PrepareQuery(vm, whereCollection, context, out totalCount);

        return new PagedResult<TAs>
        {
----------> Results = await query.ProjectTo<TAs>(mapper).ToListAsync(),
            Page = vm.Page,
            PageSize = vm.PageSize,
            Total = totalCount
        };
    }

    private static IQueryable<T> PrepareQuery<T>(PaginatedSearchBase vm, ICollection<Expression<Func<T, bool>>> whereCollection, IEnumerable<T> context,
        out int totalCount) where T : class
    {
        var query = context.AsQueryable();
        if (whereCollection != null)
        {
            foreach (var w in whereCollection)
            {
                if (w != null)
                {
                    query = query.Where(w);
                }
            }
        }
        // Order by
        query = query.OrderBy($"{vm.OrderBy} {(vm.OrderDescending ? "DESC" : "ASC")}");

        // Total rows
        totalCount = query.Count();

        // Paging
        query = query.Skip((vm.Page - 1)*vm.PageSize).Take(vm.PageSize);
        return query;
    }
}

For information, I'm using versions:

  • "Autofac": "4.0.0-rc1-177"
  • "Autofac.Extensions.DependencyInjection": "4.0.0-rc1-177"
  • "AutoMapper": "4.2.1"

Edit:

A new test that I did to check if the mappings really work:

var mapper = _container.Resolve<IMapper>();
        var p = new Patient();
        p.Naam = "Test";
        var vm = mapper.Map<PatientViewModel>(p);

        vm.Naam.ShouldBeEquivalentTo("Test");

This test passes

Edit 2:

When I use the Map<> in a Select() instead, it works too, so it's really the ProjectTo<>() that fails:

var results = await query.ToListAsync();
        return new PagedResult<TAs>
        {
            Results = results.Select(mapper.Map<TAs>).ToList(),
            Page = vm.Page,
            PageSize = vm.PageSize,
            Total = totalCount
        };

This works, but it requires the mapper to be included and not be injected and it doesn't use the ProjectTo that Automapper has for database access...

Answer

Jeff picture Jeff · Jun 7, 2016

I ran into the same issue but managed to get it working. This is happening due to the recent move, by Automapper, away from having the entire API use static methods. Now that everything is instance-based, the static extension methods no longer know about the mapping configuration and they now have to be passed into the method. I ended up registering an instance of the MapperConfiguration as IConfigurationProvider (I'm using Unity)

container.RegisterInstance(typeof (IConfigurationProvider), config);

This is injected into my query handler:

[Dependency]
public IConfigurationProvider MapperConfigurationProvider { get; set; }

Finally, the MapperConfigurationProvider is passed to the call to ProjectTo:

.ProjectTo<Payment>(MapperConfigurationProvider);

Hope this helps.