Dépendance injecter UserStore dans le démarrage de OWIN en utilisant Ninject OWIN middleware

j'ai des problèmes à créer un UserStore personnalisé en utilisant l'injection de dépendances lors de la création D'un ApplicationUserManager en utilisant le pipeline de requête OWIN.

arrière-plan

j'essaie de migrer la fonctionnalité utilisateur dans notre application web de L'utilisation du SimpleMembership à la nouvelle ASP.NET identité. Lors du démarrage d'un nouveau projet MVC 5, la mise en œuvre par défaut de l'application single page utilise ASP.Identité, utilisation du cadre Entity pour mettre en œuvre la fonctionnalité UserStore.

dans mon cas, nous utilisons déjà NHibernate comme ORM, et ninject pour implémenter l'Unité de travail afin que nous ayons une session NHibernate par demande, et je voulais faire L'ASP.Identité travailler avec notre cadre existant.

à cette fin, j'ai créé un userstore personnalisé, qui pourrait être créé en injectant les dépôts pertinents/session nhibernate, etc. Cela pourrait puis être injecté dans le constructeur du Controller en utilisant Ninject, plutôt que D'utiliser 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>(ApplicationUserManager.Create);

à la place, j'ai utilisé le NinjectWebCommon créé lors de l'installation du Ninject.Web.Commun.Paquet webhost nuget à créer les liaisons pertinentes.

cette implémentation a bien fonctionné avec certaines opérations UserManager, mais avec certaines opérations, comme ResetPasswordAsync, elle échoue parce que L'implémentation par défaut D'ApplicationUserManager n'est pas appelée, et donc le UserTokenProvider de 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, le UserTokenProvider n'est pas défini.

problème

je veux utiliser L'OWIN pipeline, parce que L'implémentation par défaut de la classe ApplicationUserManager par Visual Studio injecte le Provider Idataprotection dans sa méthode de callback Create. Cependant, je veux aussi créer mon UserStore en utilisant l'Injection de dépendances, et je ne sais pas comment créer un UserStore dans cette méthode en utilisant 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 Ninject.Web.Commun.OwinHost paquet nuget et la création du noyau dans le Démarrage de la classe.

    public void ConfigureAuth(IAppBuilder app)
    {
        // Setup

        app.UseNinjectMiddleware(CreateKernel);
    }

cependant, le Ninject.Web.Commun.OwinHost n'expose pas son noyau, donc je ne peux pas utiliser le modèle de localisation de service pour injecter les valeurs dans mon UserStore personnalisé dans la fonction Create callback.

j'ai aussi essayé de créer un noyau singleton, et l'enregistrer en utilisant app.CreatePerOwinContext (CreateKernel) avec le délégué correspondant, pour que je puisse accéder plus tard au noyau, mais quand j'appelle context.Get() retourne la valeur 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 custom UserStore en utilisant l'injection de dépendance dans le callback Create, de sorte que j'ai également accès aux options Identityfactoryutilisées par Owin pour injecter le fournisseur de token utilisateur?

39
demandé sur abatishchev 2014-06-23 22:56:32

2 réponses

pour info:

il est possible d'enregistrer le noyau comme un singleton de sorte que le même noyau puisse être utilisé par le middleware ninject et aussi 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 de l'app.CreatePerOwinContext (ApplicationUserManager.Créer), appellera L'ApplicationUserManager.Créer plutôt que de l'inscrire pour être appelé plus tard au cours de l'installation. Par conséquent, la fonction CreateKernel doit être enregistré avant le rappel Create callback de L'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 créer de L'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épendance devrait être favorisée par rapport au lieu de service, mais dans ce contexte, je vous ne pouviez pas voir un moyen de le contourner-à moins que quelqu'un ait une meilleure suggestion?

cela vous permettra d'utiliser Ninject pour mettre en œuvre l'Unité de travail patter en s'appuyant sur L'InRequestScope () de Ninject.OnDeactivation fonctionnalité. Je suis conscient que la classe UserManager a un par durée de vie de la demande , mais ne savait pas le plus la façon la plus appropriée de commettre toutes les transactions en suspens sur la finition de la demande.

15
répondu RiddleMeThis 2014-07-09 10:00:00

Note il s'agit de WebApi (en utilisant System.Web.Http )

Ok donc j'ai en quelque sorte triché en utilisant des trucs de System.Web qui est l'Espace-nom que nous sommes censés être en train de nous enlever, mais pendant qu'il est encore utilisé, pourquoi pas.

tout D'abord, j'utilise quelques helpers de cette question SO:

configurer Ninject avec Asp.Net MVC & Api Web

Dans lequel, le solveur est enregistré avec la configuration globale de System.Web . Ainsi, je vais juste l'attraper 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);
...

Note: j'utilise le terme de dépôt sur magasin car il correspond au modèle bien connu, plus compréhensible pour la plupart des gens.

et la start-up.Auth ressemble à ceci, je déplace essentiellement le Ninject init ici donc son 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 callback pour obtenir le IKernel mais bien que cela garde tout à fait propre, 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.

Note Complémentaire

c'est le code-cadre D'identité qui enregistre rappel. Notez l'appel à app.GetDataProtectionProvider qui est essentiellement la chose dont nous avions besoin à l'origine pour faire 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 j'ai réfléchi et reflété les libs et je ne trouve pas cette méthode! Si je savais comment cela fonctionnait, potentiellement nous pourrions trouver une autre façon de créer un token, c'est-à-dire que nous n'aurions pas besoin de l'instance options.

1
répondu Luke Puplett 2017-05-23 12:34:31