如何在应用程序域中仅配置一次AutoMapper

20

我的当前项目涉及到领域模型的程序集、MVC Web应用程序和单元测试。我该如何设置AutoMapper配置,以便所有程序集都引用相同的配置?

我猜我可以将Web应用程序的项放在Global.asax中,但是我该如何在单元测试中使用它?另外,如果配置位于Global.asax中,领域模型会接收到映射吗?

非常感谢,

KevDog。

4个回答

28
我们所做的是创建一个静态类,类似于 BootStrapper,并将初始化代码放在其中的一个静态方法中。我们正在进行配置文件的处理,因此在此类中看不到太多内容。Global.asax 在启动时会调用它,域将使用它(因为配置是单例),需要它的单元测试会在设置中调用 BootStrapper.Configure()。
最后一件事情是在 Bootstrapper 上保留一个标志,并在配置完成后将其设置为 true。这样,配置只会在每个应用程序域中执行一次。这意味着在 global.asax(Application_Start)启动时和运行单元测试时各执行一次。
希望这对你有帮助。

1
谢谢Jimmy!这个工具做得非常好,它在多个方面都很出色。我喜欢引导程序上的标志。今晚我会把它插入我的代码中。 - KevDog
谢谢!这对我帮助很大。至于这个工具,它非常强大,我喜欢它! - Rushino

4
我还使用一个引导程序来处理这种启动任务。实际上,我使用了一系列的引导程序,因为我就是那么疯狂。关于Automapper,我们发现通过创建一些AutoMappingBuddy类并用属性修饰它们会更加清晰。然后,我们通过一些反射调用将映射器进行连接(虽然不便宜,但只在开始时触发一次)。这个解决方案是在我们厌倦在1200多行文件的第841行找到一个Automapper问题之后发现的。

我考虑过发布代码,但我不能真正称它为漂亮的代码。无论如何,下面是代码:

首先,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>>();
}

这个东西花了大约一个小时拼凑出来,可能有更优雅的方法来实现。


Wyatt,如果你有机会写一篇关于你是如何做到这一点的文章,我一定会排队阅读。听起来很优雅。 - KevDog
这可能也是要加入到AutoMapper中的东西——对于大型配置文件非常有用。 对于下一个版本来说是一个很好的想法! - Jimmy Bogard
代码已发布。@Jimmy:如果您愿意,我可以提交一个补丁,请告诉我应该将其放在您的代码库中的哪个位置。 - Wyatt Barnett
你好,你有链接吗?我不知道你的博客在哪里,快速搜索也没有找到这篇文章。谢谢。 - roundcrisis
现在没有博客了,实际上有一个更好的版本,我应该整理一下并交给Jimmy... - Wyatt Barnett

4

我尝试使用上面的代码,但是无法让它正常工作。我稍微修改了一下代码,如下所示。我认为现在只需要通过Global.asax中的Bootstrapper调用它即可。希望这能有所帮助。

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);

2

我已经将我的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();
    }
}

我认为这样做是合适的,也有一定的逻辑性;这样做更容易理解。以前我的注册代码都被塞到一个很大的引导方法中,这已经开始变得很麻烦了。
你有什么想法?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接