Usare Ninject come IoC container di ASP.NET Identity

di Marco De Sanctis, in ASP.NET MVC, ASP.NET Identity,

I template di default di ASP.NET sono basati su OWIN e sfruttano l'IoC container contenuto nel middleware per istanziare i vari componenti di ASP.NET Identity.

Per esempio, se creiamo un nuovo progetto ASP.NET MVC e analizziamo il codice di AccountController, l'ApplicationUserManager è solitamente istanziato in questo modo:

public ApplicationUserManager UserManager
{
  get
  {
    return HttpContext.GetOwinContext()
      .GetUserManager<ApplicationUserManager>();
  }
}

Questo IoC container è configurato nel file Startup.Auth.cs:

public void ConfigureAuth(IAppBuilder app)
{
  app.CreatePerOwinContext(ApplicationDbContext.Create);
  app.CreatePerOwinContext<ApplicationUserManager>(
    ApplicationUserManager.Create);
  app.CreatePerOwinContext<ApplicationSignInManager>(
    ApplicationSignInManager.Create);

  // ...altro codice di configurazione...
}

Pur essendo idoneo nel caso di esigenze minimali, si adatta male a un contesto reale, per una serie di motivi:

  • richiede un HttpContext, mentre invece noi potremmo voler gestire le utenze in una libreria che non ha un riferimento diretto al contesto di richiesta corrente;
  • non consente un controllo diretto del ciclo di vita delle istanze;
  • non supporta la constructor injection, ma solo l'anti-pattern service locator.

Fortunatamente, con poco sforzo, possiamo modificare la configurazione di default e sfruttare il nostro IoC container preferito, con tutte le sue peculiarità. In questo esempio sfrutteremo Ninject, ma il medesimo ragionamento si può estendere a framework quali StructureMap, Unity, Castle Windsor, Autofac e quant'altro.

Fase 1: eliminare la configurazione dell'IoC di OWIN

Il primo passo da compiere è quindi proprio quello di eliminare la configurazione del container di OWIN dal file Startup.Auth.cs, che richiama i metodi Create di ApplicationUserManager, ApplicationSignInManager e ApplicationDbContext.

Esistono tuttavia dei componenti di ASP.NET Identity che vengono registrati dal framework stesso e sui quali non possiamo intervenire. Uno di questi è il DataProtectionProvider, che si occupa, tra l'altro, di generare i token necessari per reset password o attivazione utente. Questo oggetto può essere contenuto in una variabile statica così che possiamo referenziarlo più tardi. La nuova classe Startup diviene come nel codice in basso:

public static IDataProtectionProvider 
  DataProtectionProvider { get; private set; }

public void ConfigureAuth(IAppBuilder app)
{
  DataProtectionProvider = app.GetDataProtectionProvider();

  app.UseCookieAuthentication(...)

  // ... altro codice qui ...
}

Fase 2: modificare i componenti di ASP.NET Identity

Dopo questo primo passaggio, siamo pronti per eliminare i metodi Create, che a questo punto sono inutili, e spostare la logica in essi contenuta all'interno dei costruttori.

Per esempio, nel caso di ApplicationUserManager, possiamo implementare il costruttore seguente:

public ApplicationUserManager(
    IUserStore<ApplicationUser> store, 
    IdentityFactoryOptions<ApplicationUserManager> options)
  : base(store)
{
    // Configure validation logic for usernames
    this.UserValidator = new UserValidator<ApplicationUser>(this)
    {
      AllowOnlyAlphanumericUserNames = false,
      RequireUniqueEmail = true
    };

    // ... resto del codice qui ...
}

Come possiamo notare, lo UserStore non è più esplicitamente istanziato ma, più correttamente, iniettato come dipendenza nel costruttore stesso.

Una volta effettuata questa operazione per gli altri componenti di ASP.NET Identity, non ci resta che configurare il nostro IoC container.

Fase 3: configurazione del container di Ninject

Se stiamo usando Ninject, e abbiamo installato anche il package NuGet Ninject.MVC5, possiamo usare il metodo RegisterServices dentro NinjectWebCommon.cs:

private static void RegisterServices(IKernel kernel)
{
  kernel.Bind<ApplicationUserManager>()
    .ToSelf()
    .InRequestScope();

  kernel.Bind<ApplicationSignInManager>()
    .ToSelf()
    .InRequestScope();

  kernel.Bind<ApplicationDbContext>()
    .ToSelf()
    .InRequestScope();

  //.. altro codice di configurazione ..
}

Questi oggetti sono tutti in binding con se stessi, con ciclo di vita identico alla Request HTTP. In questo modo, il framework di IoC si occuperà di chiamarne il Dispose in maniera automatica, al termine del ciclo di vita della richiesta.

Oltre a questi componenti principali, dobbiamo configuare anche le dipendenze che questi utilizzano, integrando il metodo precedente con queste ulteriori righe:

kernel.Bind<IdentityFactoryOptions<ApplicationUserManager>>()
  .ToMethod(x => new IdentityFactoryOptions<ApplicationUserManager>()
  {
    DataProtectionProvider = Startup.DataProtectionProvider
  });

kernel.Bind<IUserStore<ApplicationUser>>()
 .ToMethod(x => new UserStore<ApplicationUser>(
    x.Kernel.Get<ApplicationDbContext>()))
 .InRequestScope();

kernel.Bind<IAuthenticationManager>()
  .ToMethod(x => HttpContext.Current.GetOwinContext().Authentication)
  .InRequestScope();

In particolare, nel caso di IdentityFactoryOptions abbiamo sfruttato il DataProtectionProvider che ci siamo salvati nella fase 1; per l'interfaccia IAuthenticationManager, invece, siamo costretti a recuperare il valore da restituire direttamente da OWIN, dato che la configurazione di questo elemento è immersa all'interno dell'assembly Microsoft.Owin.dll.

Fase 4: la classe AccountController

A questo punto, abbiamo praticamente ultimato tutte le operazioni. Non ci resta che modificare la classe AccountController per sfruttare correttamente la constructor injection:

public AccountController(
  ApplicationUserManager userManager, 
  ApplicationSignInManager signInManager )
{
  UserManager = userManager;
  SignInManager = signInManager;
}

public ApplicationSignInManager SignInManager { get; set; }

public ApplicationUserManager UserManager { get; set; }

Possiamo anche eliminare l'override del metodo Dispose, visto che ora il ciclo di vita di questi oggetti sarà gestito da Ninject stesso.

Conclusioni

In questo script abbiamo visto come, con un po' di modifiche sul codice di default, possiamo sfruttare il nostro IoC container preferito, in luogo del Context di OWIN, per costruire gli oggetti di ASP.NET Identity.

Non si tratta di un esercizio di stile, ma ha un impatto determinante sulle possibilità che il framework ASP.NET Identity offre, come abbiamo messo in luce nella prima parte di questo script.

Come ultima nota, sebbene in questo esempio abbiamo usato Ninject, la stessa tecnica può essere adattata a un qualsiasi framework di IoC, mantenendo del tutto inalterati i passaggi tranne, ovviamente, la fase 3 di configurazione.

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