ドメインモデル、MVC Webアプリケーション、および単体テスト用のアセンブリを使用した現在のプロジェクト。すべてのアセンブリが同じ構成を参照するようにAutoMapper構成を設定するにはどうすればよいですか?
WebアプリのGlobal.asaxにアイテムを配置できると思いますが、単体テストでそれをどのように使用できますか?また、構成がGlobal.asaxにある場合、ドメインモデルはマップを取得しますか?
どうもありがとう、
KevDog。
ドメインモデル、MVC Webアプリケーション、および単体テスト用のアセンブリを使用した現在のプロジェクト。すべてのアセンブリが同じ構成を参照するようにAutoMapper構成を設定するにはどうすればよいですか?
WebアプリのGlobal.asaxにアイテムを配置できると思いますが、単体テストでそれをどのように使用できますか?また、構成がGlobal.asaxにある場合、ドメインモデルはマップを取得しますか?
どうもありがとう、
KevDog。
BootStrapper のような静的クラスを作成し、初期化コードを静的メソッドに配置します。プロファイルを作成しているため、そこにはあまり表示されません。Global.asax は起動時にそれを呼び出し、ドメインはそれを使用し (構成がシングルトンであるため)、それを必要とする単体テストはセットアップで BootStrapper.Configure() を呼び出します。
最後に行うことは、ブートストラッパーにフラグを保持し、構成時に true に設定することです。そうすれば、構成は AppDomain ごとに 1 回だけ実行されます。つまり、global.asax (Application_Start) の起動時に 1 回、単体テストの実行時に 1 回ということです。
HTH
上記のコードを試しましたが、動作させることができませんでした。以下のように少し変更しました。あとは、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);
また、この種のスタートアップ タスクを処理するためにブートストラップも使用します。実際、私はそのようにクレイジーなので、一連のブートストラッパーを使用しています。Automapper に関しては、いくつかの AutoMappingBuddy クラスを作成し、それらを属性で装飾する方がはるかにクリーンであることがわかりました。次に、いくつかのリフレクション コールを介してマッパーを接続します (安価ではありませんが、開始時に 1 回だけ起動します)。このソリューションは、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;
}
}
3 つ目は 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>>();
}
1 時間かそこらでまとめましたが、おそらくもっと洗練された方法でそこにたどり着くことができます。
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();
}
}
私はそれが適切であり、少なくとも少し論理的だと思います。彼らはそのように従うのがはるかに簡単です。私が1つの巨大なブートストラップ法で何百もの登録をする前に、それはお尻の痛みになり始めていました。
考え?