Pregunta

Estoy configurando Automapper en Bootstrapper y llamo a Bootstrap () en Application_Start () , y me han dicho que esto está mal porque tengo para modificar mi clase Bootstrapper cada vez que tengo que agregar una nueva asignación, así que estoy violando el principio de apertura-cierre.

¿Cómo crees que 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>();
    }    
}
¿Fue útil?

Solución

Argumentaría que estás violando dos principios: el principio de responsabilidad única (SRP) y el principio de apertura / cierre (OCP).

Está violando el SRP porque la clase de arranque tiene más de una razón para cambiar: si modifica el enlace del modelo o la configuración del asignador automático.

Estaría violando el OCP si agregara un código adicional de arranque para configurar otro subcomponente del sistema.

La forma en que generalmente manejo esto 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.
    }
}

Uso Ninject para inyectar las dependencias. IConfiguration es la implementación subyacente de la clase estática AutoMapper y ModelBinderDictionary es el objeto ModelBinders.Binder . Luego definiría un NinjectModule que escanearía el ensamblaje especificado en busca de cualquier clase que implemente 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 de inicio adicional creando una clase que implementa la interfaz IGlobalConfiguration y mis clases de configuración global solo tienen una razón para cambiar.

Otros consejos

Para tenerlo completamente cerrado, podría tener un inicializador estático por registro de asignación, pero eso sería excesivo.

Algunas cosas son realmente útiles para centralizarlas hasta cierto punto desde el punto de vista de poder realizar ingeniería inversa, sin embargo.

En NInject, existe la idea 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 se ocupa precisamente de este problema. Quizás quieras revisarlo. Para evitar romper el principio de OC, debe definir sus asignadores en clases separadas que implementen IMapCreater. Boostrapper encontrará estas clases utilizando la reflexión e inicializará todos los asignadores al inicio

Si algo es el principio de responsabilidad única que estás violando, en el sentido de que la clase tiene más de una razón para cambiar.

Personalmente tendría una clase ConfigureAutoMapper con la que se realizó toda mi configuración para AutoMapper. Pero podría argumentarse que se trata de una elección personal.

Omu, lucho con preguntas similares cuando se trata de iniciar 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 esparcirla por toda la aplicación a medida que agrega cambios. Para configurar AutoMapper, creo que la ventaja de la centralización es mucho menos importante. Si puede obtener su contenedor de 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 ensamblaje o en constructores estáticos o algo descentralizado.

Básicamente, lo veo como una cuestión de decidir si quieres centralizar el arranque o descentralizarlo. Si está preocupado por el principio de apertura / cierre en su rutina de inicio, vaya con la descentralización. Sin embargo, su adhesión a OCP puede reducirse a cambio del valor de todos sus programas de arranque realizados en un solo lugar. Otra opción sería que el bootstrapper escanee ciertos ensamblajes en busca de registros, asumiendo que AutoMapper tenga tal concepto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top