Reputation: 2879
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
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:
There is a MapperConfigurationExpression
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.
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