Подтвердить что ты не робот

Зависимость впрыска 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 использует для ввода поставщика токенов пользователя?

4b9b3361

Ответ 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 и не смог найти этот метод! Если бы я знал, как это работает, потенциально мы могли бы найти другой способ создания токена, т.е. Не нужен экземпляр опций.