Dependency injection in ASP.NET MVC 5 con Ninject

di Marco De Sanctis, in ASP.NET MVC,

In progetti di una certa rilevanza, usare un IoC container porta un gran numero di benefici:

  • ci consente di astrarre le dipendenze, disaccoppiando quindi l'interfaccia dall'effettiva implementazione;
  • facilita di molto la creazione di oggetti con grafi di dipendenze complessi (A che dipende da B, che richiede C e D, ecc..);
  • gestisce in maniera del tutto automatica il ciclo di vita delle istanze create.

Esistono diversi IoC container in circolazione, praticamente tutti open source e gratuiti e, tra questi, Ninject è uno dei più versatili e semplici da utilizzare. Proviamo a capire come integrarlo nella nostra solution con un esempio.

Il progetto

Immaginiamo intanto di aver definito una classe Customer nel progetto, e di utilizzarla all'interno di un context Entity Framework:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }
}


Sebbene possiamo utilizzare direttamente MyContext all'interno dei nostri controller, solitamente si preferisce schermane l'implementazione dietro a un oggetto Repository, la cui interfaccia è grossomodo la seguente:

public interface IRepository<T>
{
    IQueryable<T> Get();
    void Add(T entity);
    void Delete(T entity);
    void Save();
}

Questo repository, poi, avrà un'implementazione concreta per la classe Customer in un CustomerRepository, che al suo interno sfrutterà il context di Entity Framework:

public class CustomerRepository : IRepository<Customer>
{
    private MyContext _context;

    public CustomerRepository(MyContext context)
    {
        _context = context;
    }

    public IQueryable<Customer> Get()
    {
        return _context.Customers;
    }

    // altri metodi ...
}

La dipendenza nei controller di ASP.NET MVC

Le pagine di gestione dei Customer, nella nostra applicazione, saranno realizzate a partire da un CustomerController, che utilizzerà il repository visto in precedenza per accedere allo strato dati.

Per disaccoppiare l'implementazione dall'interfaccia, però, non useremo direttamente CustomerRepository, ma ci riferiremo a esso solo tramite la sua interfaccia IRepository. In fase di costruzione del controller stesso, dovrà essere fornita un'istanza che esso possa utilizzare:

public class CustomersController : Controller
{
    private IRepository<Customer> _customers;
        
    public CustomersController(IRepository<Customer> customers)
    {
        _customers = customers;
    }

    // ... qui vanno le varie action ...
}

In questo modo, abbiamo definito due dipendenze:

  • CustomerController dipende da IRepository, che concretamente è implementato da CustomerRepository;
  • CustomerRepository a sua volta dipende da MyContext.

In particolare, questo controller, così costruito, non è direttamente utilizzabile da ASP.NET MVC 5, perchè non possiede un costruttore senza parametri. Se provassimo a eseguire l'applicazione, a questo punto, riceveremmo un errore simile al seguente:

Enter Ninject e Ninject.MVC3


Un IoC container come Ninject serve proprio a preoccuparsi di risolvere le dipendenze e a istanziare semplicemente i vari oggetti. Vediamo come fare.

Il primo passo consiste nell'includere nella nostra solution, il package NuGet Ninject.MVC3.


Questo package installa automaticamente una serie di librerie (tra cui, per l'appunto, Ninject stesso) e aggiunge alla cartella App_Start del progetto, una classe NinjectWebCommon che viene automaticamente eseguita allo startup dell'applicazione.

NinjectWebCommon contiene il codice necessario a configurare in ASP.NET MVC un custom ControllerFactory, in modo che sia Ninject a preoccuparsi di istanziare i vari controller e non più ASP.NET MVC; oltre questo, è presente anche un entry point, denominato RegisterServices, in cui possiamo configurare le varie dipendenze, come nell'esempio seguente:

private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<IRepository<Customer>>().To<CustomerRepository>();
    kernel.Bind<MyContext>().ToSelf().InRequestScope();
}

Nel codice in alto, abbiamo impostato Ninject in modo che, quando è richiesto un IRepository, venga creata un'istanza di CustomerRepository. La riga più interessante, però, è forse la successiva, in cui indichiamo che, quando è necessario un MyContext, deve esserne creata un'istanza da riutilizzare per tutta la durata della request.

Si tratta di una funzionalità molto potente, che ci permette di associare la stessa istanza di context anche a più repository, e di avere la certezza che esso sia disponibile fino al termine dell'esecuzione della richiesta.

Questo è tutto ciò che dobbiamo fare per riuscire finalmente a eseguire il nostro CustomerController, il cui costruttore, lo ricordiamo, richiedeva un IRepository:

public CustomersController(IRepository<Customer> customers)
{
    _customers = customers;
}

Alla richiesta, infatti, Ninject si preoccuperà automaticamente di creare un'istanza di CustomerRepository e di passarlo al costruttore, così che possiamo utilizzarlo all'interno delle varie action.

Conclusioni

Applicazioni complesse spesso definiscono molteplici dipendenze tra i vari oggetti. L'utilizzo di un IoC container come Ninject permette di semplificare di molto il processo di istanziazione e di gestione del ciclo di vita di questi ultimi.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi