使用 Ninject OWIN 中间件在 OWIN 启动中注入 UserStore

Posted

技术标签:

【中文标题】使用 Ninject OWIN 中间件在 OWIN 启动中注入 UserStore【英文标题】:Dependency injecting UserStore in OWIN startup using Ninject OWIN middleware 【发布时间】:2014-08-13 22:18:14 【问题描述】:

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

背景

我正在尝试将 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。但是,我也想使用依赖注入创建我的用户存储,我不知道如何使用依赖注入在此方法中创建用户存储。

    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。

问题

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

【问题讨论】:

【参考方案1】:

注意这是针对 WebApi(使用 System.Web.Http

好的,所以我使用来自 System.Web 的东西有点作弊,这是我们假设要减少自己的命名空间,但虽然它仍在使用,但为什么不呢。

首先,我使用了这个 SO 问题中的一些助手:

Configuring Ninject with Asp.Net MVC & Web Api

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

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

注意:我使用术语存储库而不是存储,因为它与众所周知的模式相匹配,对大多数人来说更容易理解。

而且 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 的方法,在该方法中我“附加”了一个回调来获取 IKernel,但是虽然这一切都保持不变,但这种方法的问题是你必须调用 owinContextThing.Get&lt;IKernel&gt;() 这意味着在我的代码中更深入地引用 Ninject。

有一些方法可以解决它,但它开始变得比我上面的解决方案更复杂。

补充说明

这是注册回调的身份框架代码。请注意对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;
    

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

【讨论】:

【参考方案2】:

信息:

可以将内核注册为单例,以便 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 上下文获取内核,则会得到空引用异常。

    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 的 InRequestScope().OnDeactivation 功能来实现工作单元模式。我知道 UserManager 类有一个 per request lifetime,但不知道在请求完成时提交任何未完成事务的最合适的方法。

【讨论】:

我不知道自从你的帖子以来它在 Ninject 中发生了变化,但是当你在UseNinjectMiddleware 注册CreateKernel 时,我收到错误:System.InvalidOperationException: Sequence contains no elements 来自NinjectMvcHttpApplicationPlugin . @ShayNissel 你有没有弄清楚为什么你得到一个序列不包含元素错误?我也遇到了同样的问题。 @RiddleMeThis 你见过序列不包含元素错误吗? @thebringking 我无法解决UseNinjectMiddleware 的问题,我假设(由于缺乏适当的文档)它从未针对 MVC5 进行过完全修改和测试,但仅针对 WebAPI。跨度> 为什么同时使用app.CreatePerOwinContext(CreateKernel);app.UseNinjectMiddleware(CreateKernel);?当我这样做时,它会弄乱绑定。我第一次可以从 ninject 获得绑定,但第二次尝试总是失败。删除 CreatePerOwinContext 似乎可以解决它。

以上是关于使用 Ninject OWIN 中间件在 OWIN 启动中注入 UserStore的主要内容,如果未能解决你的问题,请参考以下文章

使用 Ninject OWINHost 的 OWIN 自托管应用程序是不是需要 system.web?

更新 Microsoft.AspNet.Identity.Owin 2.2.0 后启动时处理 Ninject 内核

csharp Topshelf + OWIN自主机+ ASP.NET WebAPI + Ninject

OWIN 中间件可以使用 http 会话吗?

OWIN 中间件中的全局异常处理

OWIN 身份验证管道正确使用 Katana 中间件?