简体   繁体   English

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

[英]Dependency injecting UserStore in OWIN startup using Ninject OWIN middleware

I am having problems creating a custom UserStore using dependency injection when creating an ApplicationUserManager using the OWIN request pipeline. 在使用OWIN请求管道创建ApplicationUserManager时,使用依赖项注入创建自定义UserStore时遇到问题。

Background 背景

I am trying to migrate the user functionality in our web application from using the SimpleMembership to the new ASP.NET Identity. 我正在尝试将我们的Web应用程序中的用户功能从使用SimpleMembership迁移到新的ASP.NET标识。 When starting a new MVC 5 project, the default implementation of the single page application uses ASP.Identity, using Entity Framework to implement the UserStore functionality. 在启动新的MVC 5项目时,单页面应用程序的默认实现使用ASP.Identity,使用Entity Framework实现UserStore功能。

In my case, we are already using NHibernate as the ORM, and using ninject to implement the unit of work pattern so that we had one NHibernate session per request, and I wanted to make the ASP.Identity work with our existing framework. 在我的例子中,我们已经使用NHibernate作为ORM,并使用ninject实现工作单元模式,以便每个请求有一个NHibernate会话,我想让ASP.Identity与我们现有的框架一起工作。

To this end, I created a custom UserStore, which could be created by injecting the relevant repositories/nhibernate session, etc. This could then be injected into the Controller's constructor using Ninject, rather than using the default implementation's GetOwinContext functionality. 为此,我创建了一个自定义UserStore,可以通过注入相关的存储库/ nhibernate会话等来创建。然后可以使用Ninject将其注入Controller的构造函数,而不是使用默认实现的GetOwinContext功能。

In order to do this, I had commented out the following line in the ConfigureAuth(IAppBuilder app) method of the Startup, which by default creates the UserManager class: 为了做到这一点,我在Startup的ConfigureAuth(IAppBuilder app)方法中注释掉了以下行,默认情况下会创建UserManager类:

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

Instead, I used the NinjectWebCommon created when installing the Ninject.Web.Common.Webhost nuget package to create the relevant bindings. 相反,我使用了在安装Ninject.Web.Common.Webhost nuget包时创建的NinjectWebCommon来创建相关的绑定。

This implementation worked fine with some of the UserManager operations, but with some operations, such as ResetPasswordAsync, it fails because the default ApplicationUserManager implementation is not called, and so the UserTokenProvider in the UserManager class is never set: 这个实现在一些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;
    }

Therefore, the UserTokenProvider is not set. 因此,未设置UserTokenProvider。

Problem 问题

I want to use the OWIN pipeline, because Visual Studio's default implementation of the ApplicationUserManager class injects the IDataProtectionProvider in its Create callback method. 我想使用OWIN管道,因为Visual Studio的ApplicationUserManager类的默认实现在其Create回调方法中注入了IDataProtectionProvider。 However, I also want to create my UserStore using dependency Injection, and I do not know how to create a UserStore within this method using dependency injection. 但是,我还想使用依赖注入创建我的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);
    }

I have tried to get around this limitation by using the Ninject.Web.Common.OwinHost nuget package and creating the kernel within the Startup class. 我试图通过使用Ninject.Web.Common.OwinHost nuget包并在Startup类中创建内核来解决这个限制。

    public void ConfigureAuth(IAppBuilder app)
    {
        // Setup

        app.UseNinjectMiddleware(CreateKernel);
    }

However, the Ninject.Web.Common.OwinHost does not expose its Kernel, so I am unable to use service location pattern to inject the values into my custom UserStore in the Create callback. 但是,Ninject.Web.Common.OwinHost不公开其内核,因此我无法使用服务位置模式将值注入Create回调中的自定义UserStore。

I have also tried to create a singleton Kernel, and register this using app.CreatePerOwinContext(CreateKernel) with the relevant delegate, so I could later access the Kernel, but when I call context.Get() it just returns null. 我还试图创建一个单独的内核,并使用app.CreatePerOwinContext(CreateKernel)和相关的委托来注册它,所以我以后可以访问内核,但是当我调用context.Get()时它只返回null。

Question

How can I register a callback function with CreatePerOwinContext to create a custom UserManager which uses a custom UserStore, and then use Ninject to create the custom UserStore using dependency injection in the Create callback, so that I also have access to the IdentityFactoryOptions which Owin uses to inject the user token provider? 如何使用CreatePerOwinContext注册回调函数来创建使用自定义UserStore的自定义UserManager,然后使用Ninject在Create回调中使用依赖注入创建自定义UserStore,这样我也可以访问Owin使用的IdentityFactoryOptions注入用户令牌提供程序?

For info: 有关信息:

It is possible to register the kernel as a singleton so that the same kernel can be used by the ninject middleware and also registered within the owin context. 可以将内核注册为单例,以便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;
    }

The callback function app.CreatePerOwinContext(ApplicationUserManager.Create), will call the ApplicationUserManager.Create rather than register it to be called at a later point during the setup. 回调函数app.CreatePerOwinContext(ApplicationUserManager.Create)将调用ApplicationUserManager.Create,而不是将其注册为稍后在安装期间调用。 Therefore, the CreateKernel function needs to be registered before the ApplicationUserManager's Create callback or you will get a null reference exception if you try to get the kernel from the owin context within that method. 因此,需要在ApplicationUserManager的Create回调之前注册CreateKernel函数,否则如果尝试从该方法中的owin上下文获取内核,则会获得null引用异常。

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

This will allow you to access the kernel to create a custom UserStore within the ApplicationUserManager's Create callback: 这将允许您访问内核以在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);
        //...
    }

I know that in general dependency injection should be favoured over service location, but in this context I couldn't see a way around it - unless anybody has any better suggestions? 我知道一般情况下依赖注入应该优于服务位置,但在这种情况下我无法找到解决方法 - 除非有人有更好的建议吗?

This will allow you to use Ninject to implement the unit of work patter leveraging Ninject's InRequestScope().OnDeactivation functionality. 这将允许您使用Ninject来实现利用Ninject的InRequestScope()。OnDeactivation功能的工作单元模式。 I'm aware that the UserManager class has a per request lifetime , but didn't know the most the most appropriate way to commit any outstanding transactions on request finish. 我知道UserManager类具有每个请求的生命周期 ,但不知道在请求完成时提交任何未完成事务的最合适方式。

Note This was for WebApi (using System.Web.Http ) 注意这是针对WebApi的(使用System.Web.Http

Okay so I kind of cheated by using stuff from System.Web which is the namespace we're suppose to be weening ourselves off of, but while its still used, why not. 好吧,所以我通过使用来自System.Web东西来欺骗,这是我们假设要削弱自己的命名空间,但是虽然它仍在使用,但为什么不呢。

Firstly, I use some helpers from this SO question: 首先,我从这个SO问题中使用了一些帮助:

Configuring Ninject with Asp.Net MVC & Web Api 使用Asp.Net MVC和Web Api配置Ninject

Within which, the resolver is registered with System.Web 's global configuration. 其中,解析器在System.Web的全局配置中注册。 Thus, I just go grab it when I need it: 因此,我只是在需要时抓住它:

    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: I use the term Repository over Store since it matches the well-known pattern, more understandable to most people. 注意:我使用术语Repository over Store,因为它与众所周知的模式匹配,对大多数人来说更容易理解。

And the Startup.Auth looks like this, I basically move the Ninject init into here so its done in time: 而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);

...

I did also use a method similar to the OP where I 'attached' a callback to get the IKernel but while this keeps it all OWINy, the problem with this approach is that you have to call owinContextThing.Get<IKernel>() which means referencing Ninject deeper in my code. 我也使用了类似于OP的方法,我在其中附加了一个回调来获取IKernel但是这使得它保持全部OWINy,这种方法的问题是你必须调用owinContextThing.Get<IKernel>()这意味着在我的代码中更深入地引用Ninject。

There were ways around it, but it started getting more complex than my solution above. 有很多方法,但它开始变得比我上面的解决方案更复杂。

Additional Note 附加说明

This is the Identity Framework code that registers the callback. 这是注册回调的Identity Framework代码。 Note the call to app.GetDataProtectionProvider which is essentially the thing we originally needed to make a UserTokenProvider . 请注意对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;
    }

I've looked and looked and reflected the libs and cannot find that method! 我看了看,反映了libs,找不到那种方法! If I knew how that worked, potentially we could find another way to create a token, ie wouldn't need the options instance. 如果我知道它是如何工作的,我们可能会找到另一种创建令牌的方法,即不需要选项实例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM