2009-09-29 20 views
19

Mi proyecto actual con conjuntos para el modelo de dominio, la aplicación web MVC y las pruebas unitarias. ¿Cómo puedo configurar la configuración de AutoMapper para que todos los ensamblajes hagan referencia a la misma configuración?Cómo configurar AutoMapper una vez por dominio de aplicación

Supongo que puedo poner elementos en Global.asax para la aplicación web, pero ¿cómo puedo usar eso en las pruebas de unidad? Además, si la configuración está en Global.asax, ¿el modelo de dominio recogerá el mapa?

Muchas gracias,

KevDog.

Respuesta

28

Lo que hacemos es crear una clase estática, algo así como BootStrapper, y poner el código de inicialización en un método estático allí. Estamos haciendo perfiles, por lo que no se ve mucho allí. Global.asax lo llamará al inicio, el dominio lo usará (dado que la configuración es única), y las pruebas unitarias que lo necesitan, llame a BootStrapper.Configure() en su configuración.

Una última cosa que hacemos es mantener una bandera en el programa de arranque, y establecerlo en verdadero cuando configuramos. De esta forma, la configuración solo se ejecuta una vez por AppDomain. Eso significa que una vez que se inicia el archivo global.asax (Application_Start), y una vez cuando ejecutamos pruebas unitarias.

HTH

+1

Gracias Jimmy! Excelente trabajo en esta herramienta, oscila en múltiples niveles. Me gusta la bandera en el bootstrapper. Voy a estar cargando esto en mi código esta noche. – KevDog

+0

Gracias! Esto me ayudó mucho. En cuanto a la herramienta, es muy potente, ¡me encanta! – Rushino

4

también uso un programa previo para manejar este tipo de cosas tarea de inicio. En realidad, uso una cadena de bootstrappers porque estoy loco así. Automáticamente, encontramos que era mucho más limpio hacer algunas clases AutoMappingBuddy y decorarlas con un atributo. Luego conectamos los mapeadores a través de algunas llamadas de reflexión (no son baratas, pero solo disparan una vez al llegar). Esta solución fue descubierta después de que nos cansamos de encontrar un problema de AutoMapper en la línea 841 de un archivo de línea de más de 1200.


Pensé en publicar el código, pero realmente no puedo llamarlo así. De todos modos, aquí va:

En primer lugar, una interfaz sencilla para los AutoMappingBuddies:

public interface IAutoMappingBuddy 
{ 
    void CreateMaps(); 
} 

En segundo lugar, un poco de atributo para proporcionar un poco de pegamento:

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

En tercer lugar, la AutoMappingEngine. Es el lugar donde sucede 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>>(); 
} 

Un poco juntaron en una hora o así, probablemente hay formas más elegantes para llegar allí.

+0

Wyatt, si tiene la oportunidad de hacer una publicación sobre cómo lo hizo, ciertamente estaría en línea para leerlo. Suena elegante – KevDog

+0

Esto también puede ser algo para agregar a AutoMapper: para archivos de configuración grandes. ¡Gran idea para v.next! –

+0

Código publicado. @Jimmy: podría enviar un parche si quisiera, dígame a dónde debe ir en su código base. –

4

Probé el código anterior, pero no pude hacerlo funcionar. Lo modifiqué un poco como a continuación. Creo que todo lo que queda por hacer es llamarlo a través de Bootstrapper desde Global.asax. Espero que esto ayude.

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

y lo uso como este para asociar una fuente con un emparejamiento de destino:

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

A continuación, para crear el mapeos automágicamente, hago esto:

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

     AutoMappingEngine.CreateMappings(assembly); 
2

he sido moviendo mis llamadas AutoMapper CreateMap a clases que viven al lado de mis modelos de vista. Implementan una interfaz IAutomapperRegistrar. Utilizo la reflexión para encontrar las implementaciones de IAutoMapperRegistrar, crear una instancia y agregar los registros.

Aquí es la interfaz:

public interface IAutoMapperRegistrar 
{ 
    void RegisterMaps(); 
} 

Aquí es una implementación de la interfaz:

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

Aquí está el código que realiza los registros en mi 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(); 
    } 
} 

Me imagino que es apropiado y al menos un poco lógico; son mucho más fáciles de seguir de esa manera. Antes tenía cientos de registros en un gran método de arranque y eso comenzaba a ser un dolor en el trasero.

¿Pensamientos?

Cuestiones relacionadas