使用 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<IKernel>()
这意味着在我的代码中更深入地引用 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 内核