在使用OWIN请求管道创建ApplicationUserManager时,使用依赖项注入创建自定义UserStore时遇到问题。
背景
我正在尝试将我们的Web应用程序中的用户功能从使用SimpleMembership迁移到新的ASP.NET Identity。在启动新的MVC 5项目时,单页面应用程序的默认实现使用ASP.Identity,使用Entity Framework实现UserStore功能。
在我的情况下,我们已经使用NHibernate作为ORM,并使用ninject实现工作单元模式,以便每个请求有一个NHibernate会话,我想让ASP.Identity与我们现有的框架一起工作
为此,我创建了一个自定义UserStore,可以通过注入相关的存储库/ nhibernate会话等来创建。然后可以使用Ninject将其注入Controller的构造函数,而不是使用默认实现& #39; s 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。
问题
如何使用CreatePerOwinContext注册回调函数来创建使用自定义UserStore的自定义UserManager,然后使用Ninject在Create回调中使用依赖注入创建自定义UserStore,这样我也可以访问IdentityFactoryOptions Owin用于注入用户令牌提供程序?
答案 0 :(得分:15)
有关信息:
可以将内核注册为单例,以便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,但不知道在请求完成时提交任何未完成事务的最合适方式。
答案 1 :(得分:1)
注意这适用于WebApi(使用System.Web.Http
)
好吧所以我使用来自System.Web
的东西作弊,这是我们假设自己正在削弱的命名空间,但是当它仍在使用时,为什么不呢。
首先,我从这个问题中使用了一些助手:
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);
...
注意:我使用术语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的方法,我在其中'附加'回调来获取IKernel
,但是这使得它保持全部OWINy,这种方法的问题是你必须调用{{1这意味着在我的代码中更深入地引用Ninject。
有很多方法,但它开始变得比我上面的解决方案更复杂。
附加说明
这是注册回调的Identity Framework代码。请注意对owinContextThing.Get<IKernel>()
的调用,这基本上是我们最初需要制作app.GetDataProtectionProvider
的内容。
UserTokenProvider
我看了看并反映了libs并找不到那个方法!如果我知道它是如何工作的,我们可能会找到另一种创建令牌的方法,即不需要选项实例。