Ioc / DI – Perché devo fare riferimento a tutti i layer / assembly nell’applicazione entry?

(Relativo a questa domanda, EF4: Perché la creazione del proxy deve essere abilitata quando il caricamento lazy è abilitato? ).

Sono nuovo di DI, quindi abbi pazienza con me. Comprendo che il contenitore è responsabile dell’istanziazione di tutti i miei tipi registrati, ma per fare ciò richiede un riferimento a tutte le DLL nella mia soluzione e ai loro riferimenti.

Se non stavo usando un contenitore DI, non dovrei fare riferimento alla libreria EntityFramework nella mia app MVC3, solo al mio livello aziendale, che farebbe riferimento al mio livello DAL / Repo.

So che alla fine della giornata tutte le DLL sono incluse nella cartella bin ma il mio problema è doverlo fare riferimento esplicitamente tramite “aggiungi riferimento” in VS per poter pubblicare un WAP con tutti i file necessari.

Se non stavo usando un contenitore DI, non dovrei fare riferimento alla libreria EntityFramework nella mia app MVC3, solo il mio livello aziendale che farebbe riferimento al mio livello DAL / Repo.

Sì, è esattamente la situazione che DI lavora tanto per evitare 🙂

Con un codice strettamente accoppiato, ogni libreria può avere solo pochi riferimenti, ma questi hanno di nuovo altri riferimenti, creando un grafico profondo delle dipendenze, come questo:

Profondo grafico

Poiché il grafico delle dipendenze è profondo, significa che la maggior parte delle librerie trascina molte altre dipendenze, ad esempio nel diagramma, la Libreria C trascina la Libreria H, la Libreria E, la Biblioteca J, la Biblioteca M, la Libreria K e la Libreria N. Ciò rende più difficile riutilizzare ogni libreria indipendentemente dal resto, ad esempio durante il test dell’unità .

Tuttavia, in un’applicazione liberamente accoppiata, spostando tutti i riferimenti alla Composition Root , il grafico delle dipendenze risulta gravemente appiattito :

Shallow Graph

Come illustrato dal colore verde, è ora ansible riutilizzare la libreria C senza trascinare le eventuali dipendenze indesiderate.

Tuttavia, tutto ciò detto, con molti DI Container, non è necessario aggiungere riferimenti rigidi a tutte le librerie richieste. Invece, è ansible utilizzare l’ associazione tardiva sotto forma di assembly-scanning basata su convenzione (preferita) o configurazione XML.

Quando si esegue questa operazione, tuttavia, è necessario ricordare di copiare gli assembly nella cartella bin dell’applicazione, poiché ciò non avviene più automaticamente. Personalmente, raramente trovo che valga lo sforzo in più.

Una versione più elaborata di questa risposta può essere trovata in questo estratto da Dependency Injection in .NET, seconda edizione .

Se non stavo usando un contenitore DI, non dovrei fare riferimento alla libreria EntityFramework nella mia app MVC3

Anche quando si utilizza un contenitore DI, non è necessario consentire al progetto MVC3 di riferimento EF, ma si (implicitamente) si sceglie di farlo implementando il Composition Root (il percorso di avvio in cui si compongono i grafici degli oggetti) all’interno del progetto MVC3. Se si è molto severi nel proteggere i propri limiti architettonici mediante gli assiemi, è ansible spostare la Routine di composizione o il materiale di presentazione (MVC) in una libreria di classi.

Nella prima opzione, si lascia che il progetto MVC3 faccia riferimento a questo assemblaggio separato “bootstrapper” e farà riferimento a tutti gli altri assembly nella soluzione, facendo inoltre riferimento alla libreria dei contenitori DI. Il problema con questo è che questo progetto bootstrapper non può fare riferimento a tipi che si trovano nel progetto MVC3 (perché causerà una dipendenza da un assieme ciclico). Questi tipi devono essere spostati nel progetto bootstrapper (che probabilmente deve fare riferimento a System.Web.Mvc) oppure è necessario mantenere una piccola parte della configurazione del contenitore all’interno dell’app MVC3. Si noti inoltre che il progetto MVC fa ancora riferimento a tutti gli altri assembly indirettamente tramite il nuovo assembly bootstrapper, poiché le dipendenze di assembly sono transitive.

Sebbene posizionare la Composition Root in un assembly separato sia una cosa valida da fare, la maggior parte dei puristi DI (incluso me) di solito spostano la root di composizione in una libreria di classi quando ci sono più applicazioni finali (ad esempio un’app Web + servizio Web + servizio Windows ) che utilizzano lo stesso livello aziendale. Quando ho una singola applicazione tengo la Composition Root all’interno della mia applicazione finale.

La seconda opzione è spostare tutte le classi relative MVC (viste, controller, ecc.) Dal progetto di avvio a una libreria di classi. Ciò consente a questo nuovo assembly di presentazione di rimanere disconnesso dal resto dell’applicazione. Il tuo stesso progetto di applicazione Web diventerà una shell molto sottile con la logica di avvio richiesta. Il progetto di applicazione Web sarà la radice di composizione che fa riferimento a tutti gli altri assembly.

Estrarre la logica di presentazione in una libreria di classi può complicare le cose quando si lavora con MVC. Sarà più difficile colbind tutto, poiché i controller e le viste, le immagini, i file CSS, ecc. Non sono nel progetto di avvio. Questo è probabilmente fattibile ma richiederà più tempo per l’installazione.

Entrambe le opzioni hanno i loro lati negativi ed è per questo che generalmente consiglio di mantenere la composizione radice nel progetto web. Molti sviluppatori non vogliono che il loro assieme MVC dipenda dall’assemblaggio DAL, ma non è davvero un problema. Non dimenticare che gli assembly sono un artefatto di implementazione ; hai diviso il codice in più assiemi per consentire la distribuzione separata del codice. Uno strato architettonico d’altra parte è un artefatto logico . È molto ansible (e comune) avere più layer nello stesso assembly.

In questo caso finiremo per avere il Composition Root (layer) e il Presentation Layer nello stesso progetto di applicazione web (quindi nello stesso assembly). E anche se quell’assembly fa riferimento all’assembly che contiene il DAL, il Presentation Layer continua a non fare riferimento al Data Access Layer . Questa è una grande distinzione.

Naturalmente, quando lo facciamo, perdiamo la capacità del compilatore di controllare questa regola architettonica in fase di compilazione, ma questo non dovrebbe essere un problema. La maggior parte delle regole architettoniche non può essere verificata dal compilatore e c’è sempre qualcosa come il buon senso. E se non c’è un buon senso nel tuo team, puoi sempre usare le revisioni del codice (che ogni team dovrebbe fare sempre con IMO). Puoi anche usare uno strumento come NDepend (che è commerciale), che ti aiuta a verificare le tue regole architettoniche. Quando si integra NDepend con il processo di compilazione, si può avvisare quando qualcuno ha verificato il codice che viola tale regola architettonica.

Se non stavo usando un contenitore DI, non dovrei fare riferimento alla libreria EntityFramework nella mia app MVC3, solo il mio livello aziendale che farebbe riferimento al mio livello DAL / Repo.

È ansible creare un progetto separato chiamato “DependencyResolver”. In questo progetto devi fare riferimento a tutte le tue librerie.

Ora il Layer UI non ha bisogno di NHibernate / EF o di qualsiasi altra libreria non dell’interfaccia utente, ad eccezione di Castle Windsor per essere referenziata.

Se si desidera hide Castle Windsor e DependencyResolver dal proprio livello dell’interfaccia utente, è ansible scrivere un HttpModule che chiama il registro IoC.

Ho solo un esempio per StructureMap:

public class DependencyRegistrarModule : IHttpModule { private static bool _dependenciesRegistered; private static readonly object Lock = new object(); public void Init(HttpApplication context) { context.BeginRequest += (sender, args) => EnsureDependenciesRegistered(); } public void Dispose() { } private static void EnsureDependenciesRegistered() { if (!_dependenciesRegistered) { lock (Lock) { if (!_dependenciesRegistered) { ObjectFactory.ResetDefaults(); // Register all you dependencies here ObjectFactory.Initialize(x => x.AddRegistry(new DependencyRegistry())); new InitiailizeDefaultFactories().Configure(); _dependenciesRegistered = true; } } } } } public class InitiailizeDefaultFactories { public void Configure() { StructureMapControllerFactory.GetController = type => ObjectFactory.GetInstance(type); ... } } 

DefaultControllerFactory non utilizza direttamente il contenitore IoC, ma delega ai metodi contenitore IoC.

 public class StructureMapControllerFactory : DefaultControllerFactory { public static Func GetController = type => { throw new InvalidOperationException("The dependency callback for the StructureMapControllerFactory is not configured!"); }; protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) { return base.GetControllerInstance(requestContext, controllerType); } return GetController(controllerType) as Controller; } } 

Il delegato GetController è impostato in un registro StructureMap (in Windsor dovrebbe essere un programma di installazione).

+ Esiste una dipendenza: se un object crea un’istanza di un altro object. + Non c’è dipendenza: se un object si aspetta un’astrazione (iniezione del contruttore, iniezione del metodo …)

+ I riferimenti all’assembly (referencing dll, webservices ..) sono indipendenti dal concetto di dipendenza, perché per risolvere un’astrazione e poter compilare il codice, il layer deve fare riferimento a esso.