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
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Visualizzare le change sul plan di Terraform tramite le GitHub Actions
Short-circuiting della Pipeline in ASP.NET Core
Creare gruppi di client per Event Grid MQTT
Catturare la telemetria degli eventi di output cache in ASP.NET Core
Load test di ASP.NET Core con k6
Implementare il throttling in ASP.NET Core
Utilizzare l'operatore GroupBy come ultima istruzione di una query LINQ in Entity Framework
Gestione degli environment per il deploy con un workflow di GitHub
Routing statico e PreRendering in una Blazor Web App
Verificare la provenienza di un commit tramite le GitHub Actions
Eseguire attività basate su eventi con Azure Container Jobs
Accesso sicuro ai secrets attraverso i file in Azure Container Apps