我当前的项目包含域模型,MVC Web应用程序和单元测试的程序集。如何设置AutoMapper配置,以便所有程序集引用相同的配置?
我猜我可以在Global.asax中为web应用程序添加项目,但是如何在单元测试中使用它?此外,如果配置在Global.asax中,域模型是否会选择地图?
非常感谢,
KevDog。
答案 0 :(得分:28)
我们所做的是创建一个静态类,比如BootStrapper,并将初始化代码放在静态方法中。我们正在做个人资料,所以你在那里看不到多少。 Global.asax会在启动时调用它,域将使用它(因为配置是单例),需要它的单元测试在它们的设置中调用BootStrapper.Configure()。
我们要做的最后一件事是在引导程序上保留一个标志,并在配置时将其设置为true。这样,配置每个AppDomain只执行一次。这意味着一旦启动了global.asax(Application_Start),一次运行单元测试时。
HTH
答案 1 :(得分:4)
我还使用引导程序来处理这种启动任务。实际上,我使用了一系列bootstrappers因为我这样疯了。自动化,我们发现制作一些AutoMappingBuddy类并使用属性装饰它们更加清晰。然后我们通过一些反射调用来连接映射器(不便宜,但它们只在开始时触发一次)。在我们厌倦了在1200+行文件的第841行中发现AutoMapper问题之后发现了这个解决方案。
我想过发布代码,但我真的不能称之为purdy。无论如何,这里是:
首先,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>>();
}
有点在一个小时左右拍了一下,有可能有更优雅的方式到达那里。
答案 2 :(得分: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);
答案 3 :(得分: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();
}
}
我认为这是合适的,至少有点合乎逻辑;他们更容易遵循这种方式。之前我在一个巨大的bootstrap方法中进行了数百次注册,并且开始变得很麻烦。
思想?