Подтвердить что ты не робот

Как настроить AutoMapper один раз на AppDomain

Мой текущий проект с сборками для модели домена, веб-приложение MVC и модульные тесты. Как настроить конфигурацию AutoMapper так, чтобы все сборки ссылались на одну и ту же конфигурацию?

Я бы предположил, что я могу поместить элементы в Global.asax для веб-приложения, но как я могу использовать это в модульных тестах? Кроме того, если config находится в Global.asax, модель домена возьмет карту?

Большое спасибо,

KevDog.

4b9b3361

Ответ 1

Что мы делаем, это создать статический класс, что-то вроде BootStrapper, и поместить здесь код инициализации в статический метод. Мы делаем профили, поэтому вы не видите много там. Global.asax будет вызывать, что при запуске домен будет использовать его (поскольку конфигурация является одиночной), и модульные тесты, в которых он нуждается, вызывают BootStrapper.Configure() в их настройке.

Последнее, что мы делаем, это держать флаг на загрузочном носителе и устанавливать его в true при настройке. Таким образом, конфигурация выполняется только один раз для AppDomain. Это означает один раз при запуске global.asax(Application_Start) и один раз при запуске модульных тестов.

НТН

Ответ 2

Я также использую bootstrapper для работы с этой задачей задачи запуска. На самом деле, я использую цепочку загрузчиков, потому что я такой сумасшедший. Automapper-мудрый, мы обнаружили, что было намного проще сделать некоторые классы AutoMappingBuddy и украсить их атрибутом. Затем мы подключаем картографов через некоторые рефлекторные вызовы (не дешевые, но они срабатывают только один раз при получении). Это решение было обнаружено после того, как нам стало сложно найти проблему AutoMapper в строке 841 файла с линией 1200+.


Я подумал о публикации кода, но на самом деле я не могу назвать его пурпуным. Во всяком случае, здесь идет:

Во-первых, простой интерфейс для AutoMappingBuddies:

public interface IAutoMappingBuddy
{
    void CreateMaps();
}

Во-вторых, небольшой атрибут для предоставления некоторого клея:

public class AutoMappingBuddyAttribute : Attribute
{
    public Type MappingBuddy { get; private set; }

    public AutoMappingBuddyAttribute(Type mappingBuddyType)
    {
        if (mappingBuddyType == null) throw new ArgumentNullException("mappingBuddyType");
        MappingBuddy = mappingBuddyType;
    }

    public IAutoMappingBuddy CreateBuddy()
    {
        ConstructorInfo ci = MappingBuddy.GetConstructor(new Type[0]);
        if (ci == null)
        {
            throw new ArgumentOutOfRangeException("mappingBuddyType", string.Format("{0} does not have a parameterless constructor."));
        }
        object obj = ci.Invoke(new object[0]);
        return obj as IAutoMappingBuddy;
    }
}

В-третьих, AutoMappingEngine. Там, где происходит волшебство:

public static class AutoMappingEngine
{
    public static void CreateMappings(Assembly a)
    {
        Dictionary<Type, IAutoMappingBuddy> mappingDictionary = GetMappingDictionary(a);
        foreach (Type t in a.GetTypes())
        {
            var amba =
                t.GetCustomAttributes(typeof (AutoMappingBuddyAttribute), true).OfType<AutoMappingBuddyAttribute>().
                    FirstOrDefault();
            if (amba!= null && !mappingDictionary.ContainsKey(amba.MappingBuddy))
            {
                mappingDictionary.Add(amba.MappingBuddy, amba.CreateBuddy());
            }
        }
        foreach (IAutoMappingBuddy mappingBuddy in mappingDictionary.Values)
        {
            mappingBuddy.CreateMaps();
        }
    }

    private static Dictionary<Type, IAutoMappingBuddy> GetMappingDictionary(Assembly a)
    {
        if (!assemblyMappings.ContainsKey(a))
        {
            assemblyMappings.Add(a, new Dictionary<Type, IAutoMappingBuddy>());
        }
        return assemblyMappings[a];
    }

    private static Dictionary<Assembly, Dictionary<Type, IAutoMappingBuddy>> assemblyMappings = new Dictionary<Assembly, Dictionary<Type, IAutoMappingBuddy>>();
}

Kinda ударил вместе через час или около того, вероятно, есть более элегантные способы добраться туда.

Ответ 3

Я пробовал код выше, но не мог заставить его работать. Я немного изменил его, как показано ниже. Я думаю, что все, что осталось сделать, это назвать его через Bootstrapper из Global.asax. Надеюсь, это поможет.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using AutoMapper;

namespace Automapping
{
    public class AutoMappingTypePairing
    {
        public Type SourceType { get; set; }
        public Type DestinationType { get; set; }
    }

    public class AutoMappingAttribute : Attribute 
    {
        public Type SourceType { get; private set; }

        public AutoMappingAttribute(Type sourceType)
        {
            if (sourceType == null) throw new ArgumentNullException("sourceType");
            SourceType = sourceType; 
        }
    }

    public static class AutoMappingEngine
    {
        public static void CreateMappings(Assembly a)
        {
            IList<AutoMappingTypePairing> autoMappingTypePairingList = new List<AutoMappingTypePairing>();

            foreach (Type t in a.GetTypes())
            {
                var amba = t.GetCustomAttributes(typeof(AutoMappingAttribute), true).OfType<AutoMappingAttribute>().FirstOrDefault();

                if (amba != null)
                {
                    autoMappingTypePairingList.Add(new AutoMappingTypePairing{ SourceType = amba.SourceType, DestinationType = t});
                }
            } 

            foreach (AutoMappingTypePairing mappingPair in autoMappingTypePairingList) 
            {
                Mapper.CreateMap(mappingPair.SourceType, mappingPair.DestinationType);
            }
        }
    }
}

И я использую его так, чтобы связать источник с назначением:

[AutoMapping(typeof(Cms_Schema))]
public class Schema : ISchema
{
    public Int32 SchemaId { get; set; }
    public String SchemaName { get; set; }
    public Guid ApplicationId { get; set; }
}

Затем для автоматического создания сопоставлений я делаю следующее:

        Assembly assembly = Assembly.GetAssembly(typeof([ENTER NAME OF A TYPE FROM YOUR ASSEMBLY HERE]));

        AutoMappingEngine.CreateMappings(assembly);

Ответ 4

Я перемещаю свои вызовы AutoMapper CreateMap в классы, которые живут рядом с моими моделями просмотров. Они реализуют интерфейс IAutomapperRegistrar. Я использую отражение, чтобы найти реализации IAutoMapperRegistrar, создать экземпляр и добавить регистрацию.

Вот интерфейс:

public interface IAutoMapperRegistrar
{
    void RegisterMaps();
}

Вот реализация интерфейса:

public class EventLogRowMaps : IAutoMapperRegistrar
{
    public void RegisterMaps()
    {
        Mapper.CreateMap<HistoryEntry, EventLogRow>()
            .ConstructUsing(he => new EventLogRow(he.Id))
            .ForMember(m => m.EventName, o => o.MapFrom(e => e.Description))
            .ForMember(m => m.UserName, o => o.MapFrom(e => e.ExecutedBy.Username))
            .ForMember(m => m.DateExecuted, o => o.MapFrom(e => string.Format("{0}", e.DateExecuted.ToShortDateString())));
    }
}

Вот код, который выполняет регистрацию в моем приложении Application_Start:

foreach (Type foundType in Assembly.GetAssembly(typeof(ISaveableModel)).GetTypes())
{
    if(foundType.GetInterfaces().Any(i => i == typeof(IAutoMapperRegistrar)))
    {
        var constructor = foundType.GetConstructor(Type.EmptyTypes);
        if (constructor == null) throw new ArgumentException("We assume all IAutoMapperRegistrar classes have empty constructors.");
        ((IAutoMapperRegistrar)constructor.Invoke(null)).RegisterMaps();
    }
}

Я считаю, что это уместно и, по крайней мере, немного логично; им гораздо легче следовать этому пути. До того, как у меня было сотни регистраций в одном огромном методе бутстрапа, и это начало становиться болью в заднице.

Мысли?