Domanda

Sto configurando Automapper nel Bootstrapper e chiamo Bootstrap () in Application_Start () , e mi è stato detto che questo è sbagliato perché ho per modificare la mia classe Bootstrapper ogni volta che devo aggiungere un nuovo mapping, quindi sto violando il principio Open-Closed.

Come pensi che violi davvero questo 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>();
    }    
}
È stato utile?

Soluzione

Direi che stai violando due principi: il principio della responsabilità singola (SRP) e il principio di apertura / chiusura (OCP).

Stai violando l'SRP perché la classe di bootstrap ha più di un motivo per cambiare: se modifichi il binding del modello o la configurazione del mappatore automatico.

Si violerebbe l'OCP se si aggiungesse un codice di bootstrap aggiuntivo per la configurazione di un altro sotto-componente del sistema.

Il modo in cui di solito gestisco questo è che definisco la seguente interfaccia.

public interface IGlobalConfiguration
{
    void Configure();
}

Per ogni componente del sistema che necessita del bootstrap, creerei una classe che implementa tale interfaccia.

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 per iniettare le dipendenze. IConfiguration è l'implementazione sottostante della classe statica AutoMapper e ModelBinderDictionary è l'oggetto ModelBinders.Binder . Definirei quindi un NinjectModule che eseguirà la scansione dell'assembly specificato per qualsiasi classe che implementa l'interfaccia IGlobalConfiguration e aggiunga tali classi a un composito.

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

Vorrei quindi aggiungere il seguente codice al file Global.asax.

public class MvcApplication : HttpApplication
{
    public void Application_Start()
    {
        IKernel kernel = new StandardKernel(
            new AutoMapperModule(),
            new MvcModule(),
            new GlobalConfigurationModule()
        );

        Kernel.Get<IGlobalConfiguration>().Configure();
    }
}

Ora il mio codice di bootstrap aderisce sia a SRP che a OCP. Posso facilmente aggiungere ulteriore codice di bootstrap creando una classe che implementa l'interfaccia IGlobalConfiguration e le mie classi di configurazione globale hanno solo un motivo per cambiare.

Altri suggerimenti

Per averlo completamente chiuso, potresti avere un inizializzatore statico per la registrazione di Mapping, ma sarebbe eccessivo.

Alcune cose sono effettivamente utili per essere centralizzate in una certa misura dal punto di vista della capacità di decodificare però.

In NInject, c'è l'idea di avere un Modulo per progetto o sottosistema (insieme di progetti), che sembra un ragionevole compromesso.

So che questo è vecchio, ma potresti essere interessato a sapere che ho creato una libreria open source chiamata Bootstrapper che affronta proprio questo problema. Potresti voler dare un'occhiata. Per evitare di violare il principio OC, è necessario definire i mapper in classi separate che implementano IMapCreater. Boostrapper troverà queste classi usando reflection e inizializzerà tutti i mapper all'avvio

Se qualcosa è l'unico principio di responsabilità che stai violando, in quanto la classe ha più di un motivo per cambiare.

Personalmente avrei una classe ConfigureAutoMapper con cui è stata eseguita tutta la mia configurazione per AutoMapper. Ma si potrebbe sostenere che dipende dalla scelta personale.

Omu, ho a che fare con domande simili quando si tratta di avviare un container IoC nella routine di avvio della mia app. Per l'IoC, la guida che mi è stata data evidenzia il vantaggio di centralizzare la tua configurazione piuttosto che spargerla su tutta l'app quando aggiungi le modifiche. Per la configurazione di AutoMapper, penso che il vantaggio della centralizzazione sia molto meno importante. Se riesci a ottenere il tuo contenitore AutoMapper nel tuo contenitore IoC o Service Locator, concordo con il suggerimento di Ruben Bartelink di configurare i mapping una volta per assieme o in costruttori statici o qualcosa di decentralizzato.

Fondamentalmente, lo vedo come una questione di decidere se si desidera centralizzare il bootstrap o decentralizzarlo. Se sei così preoccupato per il principio aperto / chiuso della tua routine di avvio, procedi con il decentramento. Ma la tua aderenza a OCP può essere ridotta in cambio del valore di tutto il bootstrap fatto in un unico posto. Un'altra opzione sarebbe quella di fare in modo che il bootstrapper esegua la scansione di determinati assembly per i registri, supponendo che AutoMapper abbia un tale concetto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top