web-dev-qa-db-fra.com

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

J'ai des problèmes pour créer un UserStore personnalisé à l'aide de l'injection de dépendance 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 à page unique utilise ASP.Identity, en utilisant Entity Framework pour implémenter la fonctionnalité UserStore.

Dans mon cas, nous utilisons déjà NHibernate comme ORM, et utilisons ninject pour implémenter le modèle d'unité de travail afin que nous ayons une session NHibernate par demande, et je voulais faire fonctionner ASP.Identity avec notre framework existant.

À cette fin, j'ai créé un UserStore personnalisé, qui pourrait être créé en injectant les référentiels pertinents/la session nhibernate, 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 mis en commentaire la ligne suivante dans la méthode ConfigureAuth (application IAppBuilder) du démarrage, qui crée par défaut la classe UserManager:

// app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

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

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

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) 
    {
        var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
        // Configure validation logic for usernames
        manager.UserValidator = new UserValidator<ApplicationUser>(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<ApplicationUser>
        {
            MessageFormat = "Your security code is: {0}"
        });
        manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
        {
            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<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
        }
        return manager;
    }

Par conséquent, 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 l'IDataProtectionProvider dans sa méthode de rappel Create. Cependant, je veux également créer mon UserStore à l'aide de l'injection de dépendances, et je ne sais pas comment créer un UserStore dans cette méthode à l'aide de l'injection de dépendances.

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> 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 de nuget Ninject.Web.Common.OwinHost et en créant le noyau dans la classe Startup.

    public void ConfigureAuth(IAppBuilder app)
    {
        // Setup

        app.UseNinjectMiddleware(CreateKernel);
    }

Cependant, Ninject.Web.Common.OwinHost n'expose pas son noyau, je ne peux donc pas utiliser le modèle d'emplacement de service pour injecter les valeurs dans mon UserStore personnalisé dans le rappel de création.

J'ai également essayé de créer un noyau singleton et de l'enregistrer en utilisant app.CreatePerOwinContext (CreateKernel) avec le délégué approprié, afin que je puisse plus tard accéder au noyau, 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'injection de dépendances dans le rappel de création, de sorte que j'ai également accès aux IdentityFactoryOptions qu'Owin utilise pour injecter le fournisseur de jetons utilisateur?

39
RiddleMeThis

Pour info:

Il est possible d'enregistrer le noyau en tant que singleton afin que le même noyau 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<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

            _kernel.Load(Assembly.GetExecutingAssembly(), Assembly.Load("Super.CompositionRoot"));
        }
        return _kernel;
    }

La fonction de rappel app.CreatePerOwinContext (ApplicationUserManager.Create), appellera ApplicationUserManager.Create plutôt que de l'enregistrer pour être 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 nulle si vous essayez d'obtenir le noyau à partir du contexte owin dans cette méthode.

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

Cela vous permettra d'accéder au noyau pour créer un UserStore personnalisé dans le rappel de création d'ApplicationUserManager:

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

Je sais qu'en général, l'injection de dépendances devrait être privilégiée par rapport à l'emplacement du service, mais dans ce contexte, je ne pouvais pas voir un moyen de le contourner - à moins que quelqu'un n'ait de meilleures suggestions?

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

15
RiddleMeThis

Remarque C'était pour WebApi (en utilisant System.Web.Http)

D'accord, j'ai donc un peu triché en utilisant des trucs de System.Web Qui est l'espace de noms dont nous supposons nous débarrasser, mais tant qu'il est encore utilisé, pourquoi pas.

Tout d'abord, j'utilise des aides de cette SO question:

Configuration de Ninject avec Asp.Net MVC et Web Api

Dans lequel, le résolveur est enregistré avec la configuration globale de System.Web. Ainsi, je vais juste le saisir quand j'en ai besoin:

    public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> 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 Référentiel sur Store car il correspond au modèle bien connu, plus compréhensible pour la plupart des gens.

Et le Startup.Auth ressemble à ceci, je déplace essentiellement 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>(ApplicationUserManager.Create);

...

J'ai également utilisé une méthode similaire à l'OP où j'ai `` attaché '' un rappel pour obtenir le IKernel mais bien que cela conserve tout OWINy, le problème avec cette approche est que vous devez appeler owinContextThing.Get<IKernel>() ce qui signifie référencer Ninject plus profondément dans mon code.

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

Remarque 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.

    /// <summary>
    /// 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
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="app"></param>
    /// <param name="createCallback"></param>
    /// <returns></returns>
    public static IAppBuilder CreatePerOwinContext<T>(this IAppBuilder app, Func<IdentityFactoryOptions<T>, 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<T, IdentityFactoryOptions<T>>),
            new IdentityFactoryOptions<T>() {
                DataProtectionProvider = app.GetDataProtectionProvider(),
                Provider = new IdentityFactoryProvider<T>() {
                    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 une autre façon de créer un jeton, c'est-à-dire que nous n'aurions pas besoin de l'instance d'options.

1
Luke Puplett