首页 文章

使用Ninject OWIN中间件在OWIN启动中注入UserStore的依赖关系

提问于
浏览
39

在使用OWIN请求管道创建ApplicationUserManager时,使用依赖项注入创建自定义UserStore时遇到问题 .

Background

我正在尝试将我们的Web应用程序中的用户功能从使用SimpleMembership迁移到新的ASP.NET标识 . 在启动新的MVC 5项目时,单页面应用程序的默认实现使用ASP.Identity,使用Entity Framework实现UserStore功能 .

在我的例子中,我们已经使用NHibernate作为ORM,并使用ninject实现工作单元模式,以便每个请求有一个NHibernate会话,我想让ASP.Identity与我们现有的框架一起工作 .

为此,我创建了一个自定义UserStore,可以通过注入相关的存储库/ nhibernate会话等来创建 . 然后可以使用Ninject将其注入Controller的构造函数,而不是使用默认实现的GetOwinContext功能 .

为了做到这一点,我在Startup的ConfigureAuth(IAppBuilder app)方法中注释掉了以下行,默认情况下会创建UserManager类:

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

相反,我使用了在安装Ninject.Web.Common.Webhost nuget包时创建的NinjectWebCommon来创建相关的绑定 .

这个实现在一些UserManager操作中运行良好,但是对于一些操作,例如ResetPasswordAsync,它失败了,因为没有调用默认的ApplicationUserManager实现,因此从未设置UserManager类中的UserTokenProvider:

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;
    }

因此,未设置UserTokenProvider .

问题

我想使用OWIN管道,因为Visual Studio的ApplicationUserManager类的默认实现在其Create回调方法中注入了IDataProtectionProvider . 但是,我还想使用依赖注入创建我的UserStore,我不知道如何使用依赖注入在此方法中创建UserStore .

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);
    }

我试图通过使用Ninject.Web.Common.OwinHost nuget包并在Startup类中创建内核来解决这个限制 .

public void ConfigureAuth(IAppBuilder app)
    {
        // Setup

        app.UseNinjectMiddleware(CreateKernel);
    }

但是,Ninject.Web.Common.OwinHost不公开其内核,因此我无法使用服务位置模式将值注入Create回调中的自定义UserStore .

我还试图创建一个单独的内核,并使用app.CreatePerOwinContext(CreateKernel)和相关的委托来注册它,这样我以后可以访问内核,但是当我调用context.Get()时它只返回null .

Question

如何使用CreatePerOwinContext注册回调函数来创建使用自定义UserStore的自定义UserManager,然后使用Ninject在Create回调中使用依赖注入创建自定义UserStore,这样我也可以访问Owin使用的IdentityFactoryOptions注入用户令牌提供程序?

2 回答

  • 1

    有关信息:

    可以将内核注册为单例,以便ninject中间件可以使用相同的内核,也可以在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;
        }
    

    回调函数app.CreatePerOwinContext(ApplicationUserManager.Create)将调用ApplicationUserManager.Create,而不是将其注册为稍后在安装期间调用 . 因此,需要在ApplicationUserManager的Create回调之前注册CreateKernel函数,否则如果尝试从该方法中的owin上下文获取内核,则会获得null引用异常 .

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

    这将允许您访问内核以在ApplicationUserManager的Create回调中创建自定义UserStore:

    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);
            //...
        }
    

    我知道一般情况下依赖注入应该优于服务位置,但在这种情况下我无法找到解决方法 - 除非有人有更好的建议吗?

    这将允许您使用Ninject实现工作单元模式,利用Ninject 's InRequestScope().OnDeactivation functionality. I' m意识到UserManager类具有per request lifetime,但不知道最合适的方式在请求完成时提交任何未完成的事务 .

  • 15

    Note 这是针对WebApi的(使用 System.Web.Http

    好吧,所以我使用来自 System.Web 的东西作弊,这是我们假设自己正在削弱的命名空间,但是虽然它仍在使用,但为什么不呢 .

    首先,我使用了一些帮手这个问题:

    Configuring Ninject with Asp.Net MVC & Web Api

    其中,解析器注册了 System.Web 的全局配置 . 因此,我只是在需要时 grab 它:

    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);
    ...
    

    注意:我使用术语Repository over Store,因为它与众所周知的模式匹配,对大多数人来说更容易理解 .

    而Startup.Auth看起来像这样,我基本上将Ninject init移动到这里,以便及时完成:

    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);
    
    ...
    

    我也使用了一个类似于OP的方法,我在_259709_回调得到了 IKernel 但是这使得它保持全部OWINy,这种方法的问题是你必须调用 owinContextThing.Get<IKernel>() ,这意味着在我的代码中更深入地引用Ninject .

    有很多方法,但它开始变得比我上面的解决方案更复杂 .

    Additional Note

    这是注册回调的Identity Framework代码 . 请注意对 app.GetDataProtectionProvider 的调用,这实际上是我们最初需要制作 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;
        }
    

    我看了看,反映了libs,找不到那种方法!如果我知道它是如何工作的,我们可能会找到另一种创建令牌的方法,即不需要选项实例 .

相关问题