c# - mvc - Cómo configurar AutoMapper una vez por AppDomain
imapper c# (4)
Mi proyecto actual con ensamblajes 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?
Supongo que podría poner elementos en Global.asax para la aplicación web, pero ¿cómo puedo usar eso en las pruebas unitarias? Además, si la configuración está en Global.asax, ¿el modelo de dominio recogerá el mapa?
Muchas gracias,
KevDog.
He estado moviendo mis llamadas de AutoMapper CreateMap a clases que viven junto a mis modelos de vista. Implementan una interfaz IAutomapperRegistrar. Uso la reflexión para encontrar las implementaciones de IAutoMapperRegistrar, crear una instancia y agregar los registros.
Aquí está la interfaz:
public interface IAutoMapperRegistrar
{
void RegisterMaps();
}
Aquí hay 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 de tener cientos de registros en un gran método bootstrap y eso estaba empezando a ser un dolor en el culo.
¿Pensamientos?
Lo que hacemos es crear una clase estática, como BootStrapper, y poner el código de inicialización en un método estático allí. Estamos haciendo perfiles, así que no ves mucho allí. Global.asax lo llamará al inicio, el dominio lo usará (ya que la configuración es única), y las pruebas unitarias que lo necesiten llaman a BootStrapper.Configure () en su configuración.
Una última cosa que hacemos es mantener una bandera alrededor del bootstrapper y establecerla en verdadero cuando configuramos. De esa manera, la configuración solo se ejecuta una vez por AppDomain. Eso significa una vez en el inicio de global.asax (Application_Start), y una vez que ejecutamos pruebas unitarias.
HTH
Probé el código de arriba, pero no pude hacerlo funcionar. Lo modifiqué un poco como sigue a continuación. Creo que todo lo que queda por hacer es llamarlo a través de un Bootstrapper de 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 así 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; }
}
Luego, para crear las asignaciones automáticamente, hago esto:
Assembly assembly = Assembly.GetAssembly(typeof([ENTER NAME OF A TYPE FROM YOUR ASSEMBLY HERE]));
AutoMappingEngine.CreateMappings(assembly);
También uso un bootstrapper para manejar este tipo de tareas de inicio. En realidad, uso una cadena de bootstrappers porque estoy loco como eso. De manera automática, encontramos que era mucho más limpio hacer algunas clases de AutoMappingBuddy y decorarlas con un atributo. Luego conectamos los mapeadores a través de algunas llamadas de reflexión (no son baratas, pero solo se disparan una vez en el arranque). Esta solución se descubrió después de que nos cansamos de encontrar un problema de AutoMapper en la línea 841 de un archivo de más de 1200 líneas.
Pensé en publicar el código, pero realmente no puedo llamarlo así. De todos modos, aquí va:
Primero, una interfaz simple para AutoMappingBuddies:
public interface IAutoMappingBuddy
{
void CreateMaps();
}
En segundo lugar, un pequeño atributo para aportar algo 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, el AutoMappingEngine. Es donde ocurre 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>>();
}
Algo que abofetearon en una hora más o menos, probablemente haya formas más elegantes de llegar allí.