2009-09-29 9 views
19

Il mio progetto corrente con gli assembly per il modello di dominio, l'applicazione Web MVC e le unit test. Come posso configurare la configurazione di AutoMapper in modo che tutti gli assembly facciano riferimento alla stessa configurazione?Come configurare AutoMapper Once Per AppDomain

Immagino di poter inserire elementi in Global.asax per l'app Web, ma come posso utilizzarlo nei test delle unità? Inoltre, se la configurazione è in Global.asax, il modello di dominio prenderà la mappa?

Molte grazie,

KevDog.

risposta

28

Quello che facciamo è creare una classe statica, qualcosa come BootStrapper, e inserire il codice di inizializzazione in un metodo statico. Stiamo facendo profili, quindi non vedi molto lì dentro. Global.asax lo chiamerà all'avvio, il dominio lo userà (poiché la configurazione è singleton), e le unit test che ne hanno bisogno chiamano BootStrapper.Configure() nella loro configurazione.

Un'ultima cosa che facciamo è mantenere un flag sul bootstrapper e impostarlo su true quando configuriamo. In questo modo, la configurazione viene eseguita solo una volta per AppDomain. Ciò significa una volta all'avvio di global.asax (Application_Start) e una volta quando eseguiamo i test unitari.

HTH

+1

Grazie Jimmy! Ottimo lavoro su questo strumento, oscilla su più livelli. Mi piace la bandiera sul bootstrapper. Stasera lo porterò nel mio codice. – KevDog

+0

Grazie! Questo mi ha aiutato molto. Per quanto riguarda lo strumento, è molto potente lo adoro! – Rushino

4

Ho anche utilizzare un programma di avvio automatico per gestire questo genere di cose compito di avvio. In realtà, io uso una catena di bootstrapper perché sono pazzo del genere. Dal punto di vista di Automapper, abbiamo scoperto che era molto più pulito fare alcune classi AutoMappingBuddy e decorarle con un attributo. Quindi colleghiamo i mappatori tramite alcune chiamate di riflessione (non a buon mercato, ma sparano solo una volta al via). Questa soluzione è stata scoperta dopo che ci siamo stancati di trovare un problema AutoMapper nella riga 841 di un file di 1200+ righe.


Ho pensato di postare il codice, ma non posso davvero chiamarlo così purdy. In ogni caso, ecco qui:

In primo luogo, una semplice interfaccia per i AutoMappingBuddies:

public interface IAutoMappingBuddy 
{ 
    void CreateMaps(); 
} 

In secondo luogo, un po 'attributo per fornire po' di colla:

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

In terzo luogo, l'AutoMappingEngine. È il luogo dove avviene la magia:

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 schiaffeggiato insieme in un'ora o giù di lì, ci sono probabilmente più eleganti modi per arrivarci.

+0

Wyatt, se hai la possibilità di fare un post su come hai fatto, sarei certamente in fila per leggerlo. Sembra elegante. – KevDog

+0

Anche questo potrebbe essere qualcosa da aggiungere a AutoMapper - per file di configurazione di grandi dimensioni. Ottima idea per v.next! –

+0

Codice pubblicato. @Jimmy: Potrei inviare una patch se vuoi, fammi sapere dove dovrebbe andare nella tua base di codice. –

4

Ho provato il codice sopra, ma non sono riuscito a farlo funzionare. L'ho modificato un po 'come segue. Penso che tutto ciò che resta da fare è chiamarlo tramite un Bootstrapper di Global.asax. Spero che questo ti aiuti.

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

e lo uso come questo per associare una fonte con un abbinamento di destinazione:

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

Poi per creare le mappature automagicamente, faccio questo:

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

     AutoMappingEngine.CreateMappings(assembly); 
2

Sono stato spostare le mie chiamate CreateMap AutoMapper in classi che vivono accanto ai miei modelli di vista. Implementano un'interfaccia IAutomapperRegistrar. Uso la reflection per trovare le implementazioni IAutoMapperRegistrar, creare un'istanza e aggiungere le registrazioni.

Qui è l'interfaccia:

public interface IAutoMapperRegistrar 
{ 
    void RegisterMaps(); 
} 

Ecco un'implementazione dell'interfaccia:

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

Ecco il codice che esegue le registrazioni del mio 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(); 
    } 
} 

Immagino sia appropriato e almeno un po 'logico; sono molto più facili da seguire in questo modo. Prima avevo centinaia di registrazioni in un enorme metodo di bootstrap e questo stava iniziando a diventare un rompicoglioni.

Pensieri?

Problemi correlati