Scopo di questo post è vedere un esempio di uso di astrazione, nel caso specifico
di Dependency Injection, in un esempio concreto. In questo caso si tratta della
customizzazione di un cms open source basato su MVC3.
Sovente conviene utilizzare prodotti opensource da customizzare per realizzare un
lavoro. Per ogni caratteristica del prodotto opensource che serve per il lavoro ho già una base testata e coi bachi fissati. Tanto tempo risparmiato e
risultati concereti che possono arrivare prima.
Nella customizzazione, come nella realizzazione ex nihilo, ci si propone anche di scrivere codice
che sia riutilizzabile, facilmente analizzabile, mantenibile, testabile e riutilizzabile;
un "segreto" importante è programmare orientati alle interfacce e non alle implementazioni.
"Structure Map" è un tool open source che implementa il pattern architetturale
"Dependency Injection".
Tale pattern serve a disaccoppiare le variabili di istanza di un classe dalla
loro implementazione.
E' pertanto molto importante per evitare il cosiddetto
"spaghetti code".
Se ho una classe Client che gestisce un operazione la cui implementazione varia a
seconda del contesto in cui si svolge, oppure varia nel tempo, si procede dichiarando un interfaccia
IContext che dichiara la segnatura dell'operazione da svolgere. Il client ha poi
c'è un metodo SetContext che consente di valorizzare tale contesto,
parametro della classe, con
l'implementazione desiderata. Per rendere il tutto operativo si invoca l'operazione implementata
dal context in un metodo del client.
L'istanza della classe IContext da passare alla classe che esegue l'operazione può essere istanziata per mezzo di una factory.
La Factory può anche provvedere a istanziare la classe Client, oppure se viene
creata da un altra classe, deve essere poi passata a questa.
Veniamo ora all'uso in MVC3.
Il pattern MVC3 consente di ridefinire tutte le implementazioni dei suoi componenti.
MVC3 ha la sua classe DefaultControllerFactory che è propria di Microsoft MVC. È possibile ridefinire i metodi tramite l'ereditarietà.
La classe che eredita da DefaultControllerFactory deve essere poi istanziata nel metodo Application_Start(...) del
global.asax:
Ora vediamo come invece definire una mappatura tra interfacce e
implementazioni concrete che può essere usata per dichiarare nel contesto
dell'estrazione dei dati necessari per il caricamento di una View.
1: <%@ Page Title="Title" Language="C#" MasterPageFile="~/Areas/Admin/Views/Shared/LiquidAdmin.Master"
2: Inherits="System.Web.Mvc.ViewPage<IEnumerable<gab_Pacchetti>>" %>
gab_Pacchetti è la classe POCO del model usato dalla pagina.
Il controller PacchettiController fa estrarre i dati dal livello di accesso al
database nella riga: return View(pacchettoService.LoadPacchetti()).
Tale metodo del livello di servizio è così definito:
1: public IEnumerable<gab_Pacchetti> LoadPacchetti()
2: {
3: return pacchettoData.LoadAll();
4: }
ma pacchettoData è un implementazione di IPacchettoData, cioè
PacchettoData, che implementa i metodi di accesso ai dati:
1: public interface IPacchettoData
2: {
3:
4: IEnumerable<gab_Pacchetti> LoadAll();
5: gab_Pacchetti LoadPacchetto(int id);
6: gab_Destinazioni LoadDestinazione(int id);
7: void Save(gab_Pacchetti pacchetto);
8: void SaveDestinazione(gab_Destinazioni destinazione);
9: void SaveLuogo(gab_Luoghi luogo);
10: }
Ma dove si decide che si va ad utilizzare proprio quella implementazione?
1: public class StructureMapRegistration
2: {
3: public static void RegisterAll()
4: {
5: ObjectFactory.Initialize(x =>
6: {
7: x.AddRegistry<DatabaseRegistry>();
8: x.For<IConfigurationAdapter>().Use<ConfigurationAdapter>();
9: x.For<LoggingService>().Use<LoggingService>();
10: x.For<IPacchettoData>().Use<PacchettoData>();
11: });
12:
13: }
14: }
In questa riga: x.For<IPacchetto>().Use<Pacchetto>() nel
metodo statico RegisterAll() della classe StructureMapRegistration.
Tale metodo statico è invocato in application_start(...) del global.asax e ogni
volta che un controller invocherà metodi in cui collabora IPacchettoData, sarà
usata l'implementazione concreta PacchettoData.
La riga da scrivere in tale metodo è:
1: StructureMapRegistration.RegisterAll();
Se un giorno si dovesse procedere a ridefinire l'infrastruttura di accesso ai
dati, e di conseguenza i metodi di IPacchetto dovranno essere riscritti, bastera
scrivere la nuova implementazione, ereditante sempre da IPacchettoData,
che chiamiamo con molta fantasia NewPacchettoData, e il RegisterAll()
la riga di mapping dell'injection diventerà:
x.For<IPacchettoData>().Use<PacchettoData>();
La parte di presentazione, cioè la View e il rispettivo Controller resta
invariata. Si è potuto inserire la modifica semplicemente dicendo che per
l'interfaccia IPacchettoData va usata un'implementazione anzichè un altra.
Contesto di errore
Cosa fare qualora in un progetto che usa Ctructured Map si verifica il seguenta errore?
StructureMap Exception Code: 202
No Default Instance defined for PluginFamily Data.Abstract.IPacchettoData, AtomicCms.Common, Version=2.1.3.24087, Culture=neutral, PublicKeyToken=null.
Cioè significa che non è stato di chiarato il tipo concreto per IPacchettoData.
Occorre procedere dichiarandolo nel metodo RegisterAll() di StructureMapRegistration