.net - mvc - automapper nuget
Configurar Automapper en Bootstrapper viola el principio de Open-Closed? (5)
Estoy configurando Automapper en el Bootstrapper y llamé a Bootstrap()
en Application_Start()
, y me dijeron que esto es incorrecto porque tengo que modificar mi clase Bootstrapper
cada vez que tengo que agregar un nuevo mapeo, así que estoy violando el principio abierto-cerrado.
¿Cómo piensas, realmente violo este principio?
public static class Bootstrapper
{
public static void BootStrap()
{
ModelBinders.Binders.DefaultBinder = new MyModelBinder();
InputBuilder.BootStrap();
ConfigureAutoMapper();
}
public static void ConfigureAutoMapper()
{
Mapper.CreateMap<User, UserDisplay>()
.ForMember(o => o.UserRolesDescription,
opt => opt.ResolveUsing<RoleValueResolver>());
Mapper.CreateMap<Organisation, OrganisationDisplay>();
Mapper.CreateMap<Organisation, OrganisationOpenDisplay>();
Mapper.CreateMap<OrganisationAddress, OrganisationAddressDisplay>();
}
}
En todo caso, es el principio de responsabilidad única que estás violando, ya que la clase tiene más de un motivo para cambiar.
Personalmente, tendría una clase ConfigureAutoMapper con la que completaba toda mi configuración para AutoMapper. Pero podría argumentarse que se trata de elección personal.
Omu, lucho con preguntas similares cuando se trata de arrancar un contenedor IoC en la rutina de inicio de mi aplicación. Para IoC, la guía que me han dado apunta a la ventaja de centralizar su configuración en lugar de rociarla sobre su aplicación cuando agrega cambios. Para configurar AutoMapper, creo que la ventaja de la centralización es mucho menos importante. Si puede obtener su contenedor AutoMapper en su contenedor IoC o en el Localizador de servicios, estoy de acuerdo con la sugerencia de Ruben Bartelink de configurar las asignaciones una vez por ensamblado o en constructores estáticos o algo descentralizado.
Básicamente, lo veo como una cuestión de decidir si quieres centralizar el bootstrapping o descentralizarlo. Si le preocupa el principio abierto / cerrado en su rutina de inicio, vaya con la descentralización. Pero su adhesión a OCP se puede marcar a cambio del valor de todo el arranque realizado en un solo lugar. Otra opción sería hacer que el programa de arranque analice ciertos ensamblajes para registros, suponiendo que AutoMapper tenga dicho concepto.
Para tenerlo completamente cerrado, podría tener un inicializador estático por registro de Mapeo, pero eso sería excesivo.
Sin embargo, algunas cosas son útiles para tener un grado centralizado desde el punto de vista de la capacidad de ingeniería inversa.
En NInject, existe la noción de tener un Module
por proyecto o subsistema (conjunto de proyectos), lo que parece un compromiso razonable.
Sé que este es uno antiguo, pero podría interesarte saber que he creado una biblioteca de código abierto llamada Bootstrapper que trata precisamente este problema. Quizás quieras revisarlo. Para evitar romper el principio OC, necesita definir sus mapeadores en clases separadas que implementen IMapCreater. Boostrapper encontrará estas clases usando reflexión e inicializará todos los mapeadores al inicio
Yo diría que está violando dos principios: el principio de responsabilidad única (SRP) y el principio de apertura / cierre (OCP).
Usted está violando el SRP porque la clase de arranque tiene más de un motivo para cambiar: si modifica el enlace del modelo o la configuración del asignador automático.
Usted estaría violando el OCP si tuviera que agregar un código de arranque adicional para configurar otro subcomponente del sistema.
Cómo manejo esto generalmente es que defino la siguiente interfaz.
public interface IGlobalConfiguration
{
void Configure();
}
Para cada componente en el sistema que necesita bootstrapping crearía una clase que implementa esa interfaz.
public class AutoMapperGlobalConfiguration : IGlobalConfiguration
{
private readonly IConfiguration configuration;
public AutoMapperGlobalConfiguration(IConfiguration configuration)
{
this.configuration = configuration;
}
public void Configure()
{
// Add AutoMapper configuration here.
}
}
public class ModelBindersGlobalConfiguration : IGlobalConfiguration
{
private readonly ModelBinderDictionary binders;
public ModelBindersGlobalConfiguration(ModelBinderDictionary binders)
{
this.binders = binders;
}
public void Configure()
{
// Add model binding configuration here.
}
}
Yo uso Ninject para inyectar las dependencias. IConfiguration
es la implementación subyacente de la clase AutoMapper
estática y ModelBinderDictionary
es el objeto ModelBinders.Binder
. Luego definiría un NinjectModule
que exploraría el ensamblado especificado para cualquier clase que implementara la interfaz IGlobalConfiguration
y agregaría esas clases a un compuesto.
public class GlobalConfigurationModule : NinjectModule
{
private readonly Assembly assembly;
public GlobalConfigurationModule()
: this(Assembly.GetExecutingAssembly()) { }
public GlobalConfigurationModule(Assembly assembly)
{
this.assembly = assembly;
}
public override void Load()
{
GlobalConfigurationComposite composite =
new GlobalConfigurationComposite();
IEnumerable<Type> types =
assembly.GetExportedTypes().GetTypeOf<IGlobalConfiguration>()
.SkipAnyTypeOf<IComposite<IGlobalConfiguration>>();
foreach (var type in types)
{
IGlobalConfiguration configuration =
(IGlobalConfiguration)Kernel.Get(type);
composite.Add(configuration);
}
Bind<IGlobalConfiguration>().ToConstant(composite);
}
}
Luego agregaría el siguiente código al archivo Global.asax.
public class MvcApplication : HttpApplication
{
public void Application_Start()
{
IKernel kernel = new StandardKernel(
new AutoMapperModule(),
new MvcModule(),
new GlobalConfigurationModule()
);
Kernel.Get<IGlobalConfiguration>().Configure();
}
}
Ahora mi código de arranque se adhiere tanto a SRP como a OCP. Puedo agregar fácilmente código adicional de arranque creando una clase que implemente la interfaz IGlobalConfiguration
y mis clases de configuración global solo tienen un motivo para cambiar.