Instantiating types using AutoMapper with a DI con

2019-07-21 10:42发布

问题:

Please see the code below:

public class Test : ITest
    {
        public ITest2 _iTest2;
        public int _id;
        public string _name;

        public Test(ITest2 test2)
        {
            _iTest2 = test2;
        }
    }

    public interface ITest
    {
    }

    public class Test2 : ITest2
    {
    }

    public interface ITest2
    {

    }

    public class Test3 : ITest3
    {
        public int _id;
        public string _name;
    }

    public interface ITest3
    {

    }

I have the following in my Global.asax:

Mapper.Initialize(m =>
            {  
 m.CreateMap<DataLayer.Test3, BusinessLayer.Test>().ConstructUsing(opt => new BusinessLayer.Test(new BusinessLayer.Test2()));
});

I can map the types in my client app doing this:

cfg.CreateMap<DataLayer.Test3, BusinessLayer.Test>().ConstructUsing(opt => new BusinessLayer.Test(new BusinessLayer.Test2()));

How can I map the types using Castle Windsor instead of having to use the new keyword for Test and Test2?

I read another answer and someone suggested doing this:

 public void Install(IWindsorContainer container, IConfigurationStore store)
    {

        container.Register(Types.FromAssembly(Assembly.GetExecutingAssembly()).BasedOn(typeof(IValueResolver<,,>)));
        // container.Register(Types.FromAssembly(Assembly.GetExecutingAssembly()).BasedOn<IValueResolver>());
        container.Register(Types.FromThisAssembly().BasedOn<Profile>().WithServiceBase());
        var profiles = container.ResolveAll<Profile>();

        // Add your list of profiles to the mapper configuration here
        Mapper.Initialize(m => {
            m.ConstructServicesUsing(container.Resolve);
            profiles.ToList().ForEach(p => m.AddProfile(p));
        });

        // I'm not sure about this as I haven't used AutoMapper for a while,
        // but I assume you want to resolve the static mapper instance
        container.Register(Component.For<IMapper>().Instance(Mapper.Instance));
    }

Do I have to do this:

cfg.CreateMap<DataLayer.Test3, BusinessLayer.Test>().ConstructUsing(opt => new BusinessLayer.Test(new BusinessLayer.Test2()));

or should AutoMapper be able to map the types using this:

cfg.CreateMap<DataLayer.Test3, BusinessLayer.Test>()

回答1:

In order to get AutoMapper to use Windsor to create the target type, you need to configure two things:

  1. Tell AutoMapper to construct services using Windsor
  2. Tell AutoMapper (per-mapping) to actually use the above configuration

     var container = new WindsorContainer();
    
        Mapper.Initialize(m =>
        {
            m.ConstructServicesUsing(container.Resolve);
    
            m.CreateMap<Test3, ITest>().ConstructUsingServiceLocator(); // This is important!
    
        });
    
        container.Register(Component.For<ITest>().ImplementedBy<Test>());
        container.Register(Component.For<ITest2>().ImplementedBy<Test2>());
        container.Register(Component.For<ITest3>().ImplementedBy<Test3>());
    
        var test3 = new Test3();
        var test1 = Mapper.Instance.Map<Test3, ITest>(test3);
    


回答2:

For anyone requiring this using xamarin 3.6, prism 7.1 and automapper 8.1, this is what worked for me.

In the App.xml.cs file

protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
/// other registrations ...

            containerRegistry.RegisterSingleton<IMapperProvider, MapperProvider>();
            containerRegistry.RegisterInstance(typeof(IMapper), GetMapper(containerRegistry));
 }

        /// <summary>
        /// This function required in order for injection into custom automapper resolvers
        /// </summary>
        private IMapper GetMapper(IContainerRegistry container)
        {
            var mp = container.GetContainer().Resolve<IMapperProvider>(new[] { container });
            return mp.GetMapper();
        }

The mapper provider looks like this:

public class MapperProvider : IMapperProvider
    {
        private readonly IContainerRegistry _container;

        public MapperProvider(IContainerRegistry container)
        {
            _container = container;
        }

        public IMapper GetMapper()
        {
            var config = new MapperConfiguration(cfg =>
            {
                cfg.ConstructServicesUsing(t => _container.GetContainer().Resolve(t));
                // any custom profile statement such as
                cfg.AddProfile<MappingSourcesProfile>();
                // ....
            });

            return config.CreateMapper();
        }
    }

Now my custom resolvers works, for example:

    public class BarcodesResolver : IValueResolver<repo.Book, Book, List<Barcode>>
    {
        private readonly IMapper _mapper;

        public BarcodesResolver(IMapper mapper)
        {
            _mapper = mapper;
        }

        public List<Barcode> Resolve(repo.Book source, Book destination, List<Barcode> destMember, ResolutionContext context)
        {
            repo.BookAttributes groupedAttribs = JsonConvert.DeserializeObject<repo.BookAttributes>(source.BookAttributes);

            return _mapper.Map<List<repo.Barcode>, List<Barcode>>(groupedAttribs.Barcodes);
        }
    }

The hard parts to work out here were how to specify that the containerRegistry needed to be passed into the constructor of the MapperProvider. There may be a better way of doing this but at least this works.

Also arriving at the line cfg.ConstructServicesUsing(t => _container.GetContainer().Resolve(t)); was quite obscure as there seem to be few examples out there.