2009-09-29 10 views
19

Mein aktuelles Projekt mit Assemblys für das Domänenmodell, die MVC-Webanwendung und Komponententests. Wie kann ich die AutoMapper-Konfiguration so einrichten, dass alle Baugruppen auf die gleiche Konfiguration verweisen?Konfigurieren von AutoMapper einmal pro AppDomain

Ich würde vermuten, dass ich Artikel in Global.asax für die Web-App, aber wie kann ich das in den Unit-Tests verwenden? Wenn sich die Konfiguration in Global.asax befindet, wird das Domänenmodell die Karte auch aufnehmen?

Vielen Dank,

KevDog.

Antwort

28

Wir erstellen eine statische Klasse, etwa BootStrapper, und setzen den Initialisierungscode in eine statische Methode. Wir machen Profile, also sieht man dort nicht viel. Global.asax ruft das beim Start auf, domain verwendet es (da die Konfiguration Singleton ist), und Unit-Tests, die es benötigen, rufen die BootStrapper.Configure() in ihrem Setup auf.

Eine letzte Sache, die wir tun, ist, ein Flag auf dem Bootstrapper herum zu halten, und setzen Sie es auf wahr, wenn wir konfigurieren. Auf diese Weise wird die Konfiguration nur einmal pro Anwendungsdomäne ausgeführt. Das bedeutet einmal beim Start von global.asax (Application_Start) und einmal beim Ausführen von Komponententests.

HTH

+1

Danke Jimmy! Hervorragende Arbeit an diesem Tool, es rockt auf mehreren Ebenen. Ich mag die Flagge auf dem Bootstrapper. Ich werde das heute Abend in meinen Code stecken. – KevDog

+0

Danke! Das hat mir sehr geholfen. Wie für das Werkzeug ist es sehr mächtig, ich liebe es! – Rushino

4

Ich benutze auch ein Bootstrap-Programm diese Art der Sache Start Aufgabe zu bewältigen. Eigentlich benutze ich eine Kette von Bootstrappern, weil ich so verrückt bin. Automapperweise fanden wir, dass es viel sauberer war, einige AutoMappingBuddy-Klassen zu erstellen und sie mit einem Attribut zu dekorieren. Wir verkabeln die Mapper dann über einige Reflektionsaufrufe (nicht billig, aber sie feuern nur einmal auf der Stelle). Diese Lösung wurde entdeckt, nachdem wir es satt hatten, ein AutoMapper-Problem in Zeile 841 einer Datei mit mehr als 1200 Zeilen zu finden.


Ich dachte über das Posten des Codes, aber ich kann es wirklich nicht so purdy nennen. Wie auch immer, hier geht:

Zuerst eine einfache Schnittstelle für die AutoMappingBuddies:

public interface IAutoMappingBuddy 
{ 
    void CreateMaps(); 
} 

Zweitens ein wenig Attribut etwas Klebstoff zu bieten:

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

Drittens die AutoMappingEngine. Es ist, wo die Magie passiert:

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 zusammen in einer Stunde geschlagen oder so, es gibt wahrscheinlich elegantere Wege dorthin zu gelangen.

+0

Wyatt, wenn du die Chance bekommst, einen Beitrag darüber zu schreiben, wie du das gemacht hast, wäre ich bestimmt bereit, es zu lesen. Es klingt elegant. – KevDog

+0

Dies könnte auch dem AutoMapper hinzugefügt werden - für große Konfigurationsdateien. Tolle Idee für v.next! –

+0

Code veröffentlicht. @Jimmy: Ich könnte einen Patch einreichen, wenn Sie möchten, lassen Sie mich wissen, wo es in Ihrer Codebasis gehen soll. –

4

Ich habe den Code oben versucht, konnte es aber nicht zum Laufen bringen. Ich habe es ein wenig wie folgt geändert. Ich denke, man muss es nur noch über einen Bootstrapper von Global.asax aufrufen. Hoffe das hilft.

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

Und ich verwende es so eine Quelle mit einem Ziel-Paarung zu assoziieren:

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

Dann automatisch die Zuordnungen zu erstellen, kann ich dies tun:

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

     AutoMappingEngine.CreateMappings(assembly); 
2

ich gewesen sein Verschieben meiner AutoMapper CreateMap-Aufrufe in Klassen, die neben meinen Ansichtsmodellen leben. Sie implementieren eine IAutomapperRegistrar-Schnittstelle. Ich verwende Reflektion, um die IAutoMapperRegistrar-Implementierungen zu finden, eine Instanz zu erstellen und die Registrierungen hinzuzufügen. Hier

ist die Schnittstelle:

public interface IAutoMapperRegistrar 
{ 
    void RegisterMaps(); 
} 

Hier ist eine Implementierung der Schnittstelle:

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

Hier ist der Code, der die Eintragungen in meinem Application_Start führt:

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

Ich denke, es ist angemessen und zumindest ein bisschen logisch; Sie sind viel einfacher zu folgen. Zuvor hatte ich Hunderte von Registrierungen in einer einzigen Bootstrap-Methode, und das fing an, zu einer Nervensäge zu werden.

Gedanken?