Timur Lemeshko
Timur Lemeshko

Reputation: 2879

AutoMapper AutoMapperMappingException

I've found some very strange behaviour of AutoMapper. This simple code

internal class Program
{
    private static void Main(string[] args)
    {
        Mapper.Initialize(cfg => { cfg.CreateMap<MyClass1, MyClass2>(); });
        Mapper.Initialize(cfg => { cfg.CreateMap<MyClass3, MyClass4>(); });

        var dto = new MyClass1();
        Mapper.Map<MyClass1, MyClass2>(dto);
    }
}

public class MyClass1
{
}

public class MyClass2
{
}

public class MyClass3
{
}

public class MyClass4
{
}

generates exception:

Missing type map configuration or unsupported mapping.

Mapping types: MyClass1 -> MyClass2 ConsoleApplication2.MyClass1 -> ConsoleApplication2.MyClass2

but if change order of two initialize lines like this

Mapper.Initialize(cfg => { cfg.CreateMap<MyClass3, MyClass4>(); });
Mapper.Initialize(cfg => { cfg.CreateMap<MyClass1, MyClass2>(); });

everything is fine. What's wrong? What's going on?

Upvotes: 1

Views: 1454

Answers (1)

Callum Linington
Callum Linington

Reputation: 14417

Long story short, Jimmy temporarily moved away from the static stuff in AutoMapper in favour of instance based mapping. However, as Jimmy commented:

The static mapper is not removed, just the ability to call CreateMap anywhere in the code

The answer to question what is wrong is that you're trying to initialise twice, rather then once.

To answer the next question about how to have different configurations scattered across the code, you use Profile

To answer how you configure all of it, please see below:


For AutoMapper 5.1.1

There is a MapperConfigurationExpression

https://github.com/AutoMapper/AutoMapper/blob/master/src/AutoMapper/Configuration/MapperConfigurationExpression.cs

You can pass to the mapper, or Mapper takes a Action<IMapperConfigurationExpression>.

IMapperConfigurationExpression exposes this:

void AddProfile(Profile profile)

So you can pretty much do the same as below, but register everything against a IMapper interface, which is what it seems like 4.2.1 was heading towards.

For AutoMapper 4.2.1 (Short Intro to Profiles)

Here is an example profile:

using AutoMapper;

using TreasuryRecords.Database.Models;
using TreasuryRecords.Requests.Account.Models;

public class AccountMappings : Profile
{
    protected override void Configure()
    {
        this.CreateMap<RegisterDto, Client>()
            .ForMember(x => x.UserName, c => c.MapFrom(x => x.Email));
    }
}

Here is an example of how I register my profiles:

using System;
using System.Linq;
using System.Reflection;

using AutoMapper;

using TreasuryRecords.Requests.Authenticate.Login;

public static class AutoMapperConfig
{
    public static void Configure()
    {
        Assembly
            .GetExecutingAssembly()
            .RegisterConfigurations();

        typeof(LoginRequest)
            .Assembly
            .RegisterConfigurations();
    }

    public static void RegisterConfigurations(this Assembly assembly)
    {
        var types = assembly.GetTypes();

        var automapperProfiles = types
                                    .Where(x => typeof(Profile).IsAssignableFrom(x))
                                    .Select(Activator.CreateInstance)
                                    .OfType<Profile>()
                                    .ToList();

        // so here you can pass in the instance of mapper
        // I just use the static for ease
        automapperProfiles.ForEach(Mapper.Configuration.AddProfile);
    }
}

This is how I add it to DI:

public static void RegisterAutoMapper(this IUnityContainer container)
{
    container.RegisterType<IMapper>(new InjectionFactory(_ => Mapper.Instance));
}

I'm using unity here, but it is pretty simple, just register Mapper.Instance against the interface IMapper.

Then I inject IMapper and use it like so:

this.mapper.Map<Client>(message.RegistrationDetails);

Upvotes: 2

Related Questions