Зависимость впрыска UserStore в запуске OWIN с использованием промежуточного ПО Ninject OWIN
У меня возникают проблемы с созданием пользовательского UserStore с использованием инъекции зависимостей при создании ApplicationUserManager с использованием конвейера запросов OWIN.
Фон
Я пытаюсь перенести пользовательские функции в нашем веб-приложении с помощью SimpleMembership на новую идентификацию ASP.NET. При запуске нового проекта MVC 5 стандартная реализация одностраничного приложения использует ASP.Identity, используя Entity Framework для реализации функций UserStore.
В моем случае мы уже используем NHibernate в качестве ORM и используем ninject для реализации единицы шаблона работы, так что у нас был один сеанс NHibernate для каждого запроса, и я хотел сделать работу ASP.Identity с нашей существующей структурой.
С этой целью я создал пользовательский UserStore, который можно было создать, введя соответствующий сеанс репозиториев /nhibernate и т.д. Затем его можно было бы ввести в конструктор Controller с использованием Ninject, а не использовать функциональность GetOwinContext по умолчанию.
Чтобы сделать это, я прокомментировал следующую строку в приложении ConfigureAuth (IAppBuilder) для Startup, который по умолчанию создает класс UserManager:
// app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
Вместо этого я использовал NinjectWebCommon, созданный при установке пакета Ninject.Web.Common.Webhost nuget для создания соответствующих привязок.
Эта реализация отлично работала с некоторыми операциями UserManager, но с некоторыми операциями, такими как ResetPasswordAsync, она терпит неудачу, потому что реализация по умолчанию ApplicationUserManager не вызывается, поэтому UserTokenProvider в классе UserManager никогда не устанавливается:
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 вводит IDataProtectionProvider в свой метод обратного вызова Create. Однако я также хочу создать свой UserStore с использованием зависимостей Injection, и я не знаю, как создать 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 не раскрывает свое ядро, поэтому я не могу использовать шаблон местоположения службы, чтобы вставлять значения в мой пользовательский UserStore в обратном вызове Create.
Я также попытался создать одноэлементное ядро и зарегистрировать его с помощью app.CreatePerOwinContext(CreateKernel) с соответствующим делегатом, чтобы позже получить доступ к ядру, но когда я вызываю context.Get(), он просто возвращает null.
Вопрос
Как я могу зарегистрировать функцию обратного вызова с помощью CreatePerOwinContext для создания пользовательского UserManager, который использует пользовательский UserStore, а затем использовать Ninject для создания пользовательского UserStore с использованием инъекции зависимостей в обратном вызове Create, так что у меня также есть доступ к IdentityFactoryOptions, который Owin использует для ввода поставщика токенов пользователя?
Ответы
Ответ 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, а не зарегистрирует ее для вызова в более поздней точке во время настройки. Следовательно, функция CreateKernel должна быть зарегистрирована перед обратным вызовом ApplicationUserManager Create или вы получите исключение ссылочной ссылки, если попытаетесь получить ядро из контекста owin в этом методе.
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(CreateKernel);
app.UseNinjectMiddleware(CreateKernel);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
}
Это позволит вам получить доступ к ядру для создания пользовательского UserStore в ApplicationUserManager Create callback:
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 для реализации блока работы patter, использующего Ninject InRequestScope(). Функциональность OnDeactivation. Я знаю, что класс UserManager имеет для каждого времени выполнения запроса, но не знал наиболее подходящего способа совершения каких-либо незавершенных транзакций по завершении запроса.
Ответ 2
Примечание Это было для WebApi (с помощью System.Web.Http
)
Хорошо, поэтому я как бы обманутый, используя материал из System.Web
, который является пространством имен, которое, как мы полагаем, нужно отбирать, но пока оно все еще используется, почему бы и нет.
Во-первых, я использую некоторые помощники из этого SO-вопроса:
Настройка Ninject с помощью 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
, но пока это все делает OWINy, проблема с этим подходом заключается в том, что вы должны называть owinContextThing.Get<IKernel>()
, что означает ссылаясь на Ninject глубже в моем коде.
Были вокруг, но это начало усложняться, чем мое решение выше.
Дополнительная заметка
Это код 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 и не смог найти этот метод! Если бы я знал, как это работает, потенциально мы могли бы найти другой способ создания токена, т.е. Не нужен экземпляр опций.