Dépendance injectant UserStore au démarrage d’OWIN à l’aide du middleware Ninject OWIN

J’ai du mal à créer un UserStore personnalisé à l’aide d’dependency injection lors de la création d’un ApplicationUserManager à l’aide du pipeline de requêtes OWIN.

Contexte

J’essaie de migrer la fonctionnalité utilisateur de notre application Web de l’utilisation de SimpleMembership vers la nouvelle identité ASP.NET. Lors du démarrage d’un nouveau projet MVC 5, l’implémentation par défaut de l’application à une seule page utilise ASP.Identity, en utilisant Entity Framework pour implémenter la fonctionnalité UserStore.

Dans mon cas, nous utilisons déjà NHibernate comme ORM, et nous utilisons ninject pour implémenter le modèle d’unité de travail afin d’avoir une session NHibernate par requête et je voulais que ASP.Identity fonctionne avec notre structure existante.

À cette fin, j’ai créé un UserStore personnalisé, qui pourrait être créé en injectant les référentiels / sessions nhibernate appropriés, etc. Cela pourrait ensuite être injecté dans le constructeur du Contrôleur en utilisant Ninject, plutôt qu’en utilisant la fonctionnalité GetOwinContext de l’implémentation par défaut.

Pour ce faire, j’avais commenté la ligne suivante dans la méthode ConfigureAuth (IAppBuilder app) du démarrage, qui crée par défaut la classe UserManager:

// app.CreatePerOwinContext(ApplicationUserManager.Create); 

Au lieu de cela, j’ai utilisé le NinjectWebCommon créé lors de l’installation du package nuget Ninject.Web.Common.Webhost pour créer les liaisons appropriées.

Cette implémentation a bien fonctionné avec certaines opérations UserManager, mais avec certaines opérations, telles que ResetPasswordAsync, elle échoue car l’implémentation par défaut d’ApplicationUserManager n’est pas appelée, et UserTokenProvider dans la classe UserManager n’est jamais défini:

  public static ApplicationUserManager Create(IdentityFactoryOptions options, IOwinContext context) { var manager = new ApplicationUserManager(new UserStore(context.Get())); // Configure validation logic for usernames manager.UserValidator = new UserValidator(manager) { AllowOnlyAlphanumericUserNames = false, RequireUniqueEmail = true }; // Configure validation logic for passwords manager.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = true, RequireDigit = true, RequireLowercase = true, RequireUppercase = true, }; // Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user // You can write your own provider and plug in here. manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider { MessageFormat = "Your security code is: {0}" }); manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider { Subject = "Security Code", BodyFormat = "Your security code is: {0}" }); manager.EmailService = new EmailService(); manager.SmsService = new SmsService(); var dataProtectionProvider = options.DataProtectionProvider; if (dataProtectionProvider != null) { manager.UserTokenProvider = new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")); } return manager; } 

Par conséquent, le UserTokenProvider n’est pas défini.

Problème

Je souhaite utiliser le pipeline OWIN, car l’implémentation par défaut de Visual Studio de la classe ApplicationUserManager injecte le IDataProtectionProvider dans sa méthode de rappel Create. Cependant, je veux aussi créer mon UserStore en utilisant l’dependency injection et je ne sais pas comment créer un UserStore dans cette méthode en utilisant l’dependency injection.

  public static ApplicationUserManager Create(IdentityFactoryOptions options, IOwinContext context) { // WANT TO CREATE THE USER STORE USING NINJECT DEPENDENCY INJECTION HERE // var userStore = ... var manager = new ApplicationUserManager(userStore); } 

J’ai essayé de contourner cette limitation en utilisant le package nuget Ninject.Web.Common.OwinHost et en créant le kernel dans la classe Startup.

  public void ConfigureAuth(IAppBuilder app) { // Setup app.UseNinjectMiddleware(CreateKernel); } 

Cependant, Ninject.Web.Common.OwinHost n’expose pas son kernel. Je ne peux donc pas utiliser le modèle d’emplacement du service pour injecter les valeurs dans mon UserStore personnalisé dans le rappel Create.

J’ai également essayé de créer un kernel singleton et de l’enregistrer en utilisant app.CreatePerOwinContext (CreateKernel) avec le délégué approprié, afin de pouvoir accéder ultérieurement au kernel, mais lorsque j’appelle context.Get (), il renvoie simplement null.

Question

Comment puis-je enregistrer une fonction de rappel avec CreatePerOwinContext pour créer un UserManager personnalisé qui utilise un UserStore personnalisé, puis utiliser Ninject pour créer le UserStore personnalisé à l’aide de l’dependency injection dans le rappel Create, afin d’avoir également access à IdentityFactoryOptions injecter le fournisseur de jeton utilisateur?

Pour info:

Il est possible d’enregistrer le kernel en tant que singleton afin que le même kernel puisse être utilisé par le middleware ninject et également enregistré dans le contexte owin.

  public static StandardKernel CreateKernel() { if (_kernel == null) { _kernel = new StandardKernel(); _kernel.Bind().To(); _kernel.Load(Assembly.GetExecutingAssembly(), Assembly.Load("Super.CompositionRoot")); } return _kernel; } 

La fonction de rappel app.CreatePerOwinContext (ApplicationUserManager.Create) appellera ApplicationUserManager.Create au lieu de l’enregistrer pour qu’elle soit appelée ultérieurement lors de l’installation. Par conséquent, la fonction CreateKernel doit être enregistrée avant le rappel Create de ApplicationUserManager ou vous obtiendrez une exception de référence null si vous essayez d’obtenir le kernel du contexte owin dans cette méthode.

  public void ConfigureAuth(IAppBuilder app) { app.CreatePerOwinContext(CreateKernel); app.UseNinjectMiddleware(CreateKernel); app.CreatePerOwinContext(ApplicationUserManager.Create); } 

Cela vous permettra d’accéder au kernel pour créer un UserStore personnalisé dans le rappel Create de l’application ApplicationUserManager:

  public static ApplicationUserManager Create(IdentityFactoryOptions options, IOwinContext context) { var kernel = context.Get(); var userStore = kernel.Get>(); var manager = new ApplicationUserManager(userStore); //... } 

Je sais qu’en général, l’dependency injection devrait être privilégiée par rapport à l’emplacement du service, mais dans ce contexte, je ne pouvais pas la contourner – à moins que quelqu’un ait de meilleures suggestions?

Cela vous permettra d’utiliser Ninject pour implémenter l’unité de travail en utilisant la fonctionnalité InRequestScope (). OnDeactivation de Ninject. Je suis conscient que la classe UserManager a une durée de vie par requête , mais ne connaissait pas le moyen le plus approprié pour valider les transactions en attente à la fin de la demande.

Note C’était pour WebApi (en utilisant System.Web.Http )

Ok, donc j’ai un peu sortingché en utilisant des choses de System.Web qui est l’espace de noms que nous sums supposés nous débarrasser, mais tant qu’il est encore utilisé, pourquoi pas.

Premièrement, j’utilise des aides de cette question SO:

Configuration de Ninject avec Asp.Net MVC & Web Api

Le résolveur est enregistré dans la configuration globale de System.Web . Donc, je vais juste le chercher quand j’en ai besoin:

  public static ApplicationUserManager Create(IdentityFactoryOptions options, IOwinContext context) { var repository = System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver .GetService(typeof(Data.Repositories.UserRepository)) as Data.Repositories.UserRepository; var manager = new ApplicationUserManager(repository); ... 

Remarque: J’utilise le terme Repository sur Store car il correspond au modèle bien connu, plus compréhensible pour la plupart des utilisateurs.

Et le Startup.Auth ressemble à ceci, je déplace fondamentalement l’initialisation Ninject ici, donc c’est fait à temps:

  public void ConfigureAuth(IAppBuilder app) { // Dependency Injection Evoq.AppName.Configuration.Ninject.NinjectHttpContainer.RegisterAssembly(); // Configure the db context and user manager to use a single instance per request app.CreatePerOwinContext(ApplicationUserManager.Create); ... 

J’ai aussi utilisé une méthode similaire à l’OP où j’ai «attaché» un rappel pour obtenir le IKernel mais bien que cela conserve tout le problème, le problème avec cette approche est que vous devez appeler owinContextThing.Get() référencement Ninject plus profond dans mon code.

Il y avait des façons de contourner cela, mais cela a commencé à devenir plus complexe que ma solution ci-dessus.

Note supplémentaire

Il s’agit du code Identity Framework qui enregistre le rappel. Notez l’appel à app.GetDataProtectionProvider qui est essentiellement la chose dont nous avions besoin à l’origine pour créer un UserTokenProvider .

  ///  /// Registers a callback that will be invoked to create an instance of type T that will be stored in the OwinContext which can fetched via context.Get ///  ///  ///  ///  ///  public static IAppBuilder CreatePerOwinContext(this IAppBuilder app, Func, IOwinContext, T> createCallback) where T : class,IDisposable { if (app == null) { throw new ArgumentNullException("app"); } if (createCallback == null) { throw new ArgumentNullException("createCallback"); } app.Use(typeof(IdentityFactoryMiddleware>), new IdentityFactoryOptions() { DataProtectionProvider = app.GetDataProtectionProvider(), Provider = new IdentityFactoryProvider() { OnCreate = createCallback } }); return app; } 

J’ai regardé et regardé et réfléchi les bibliothèques et je ne trouve pas cette méthode! Si je savais comment cela fonctionnait, nous pourrions potentiellement trouver un autre moyen de créer un jeton, c’est-à-dire ne pas avoir besoin de l’instance d’options.