Зарегистрировать IAuthenticationManager с помощью простого инжектора

У меня есть настройка конфигурации для Simple Injector, где я переместил все мои записи в конвейер OWIN.

Теперь проблема в том, что у меня есть контроллер AccountController, который фактически принимает параметры как

public AccountController(
    AngularAppUserManager userManager, 
    AngularAppSignInManager signinManager, 
    IAuthenticationManager authenticationManager)
{
    this._userManager = userManager;
    this._signInManager = signinManager;
    this._authenticationManager = authenticationManager;
}

Теперь мои конфигурации Owin Pipeline выглядят примерно так:

public void Configure(IAppBuilder app)
{
    _container = new Container();
    ConfigureOwinSecurity(app);
    ConfigureWebApi(app);
    ConfigureSimpleinjector(_container);

    app.Use(async (context, next) =>
    {
        _container.Register<IOwinContext>(() => context);
        await next();
    });

    _container.Register<IAuthenticationManager>(
        () => _container.GetInstance<IOwinContext>().Authentication);

    _container.Register<SignInManager<Users, Guid>, AngularAppSignInManager>();
}

private static void ConfigureOwinSecurity(IAppBuilder app)
{
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        CookieName = "AppNgCookie",
        //LoginPath = new PathString("/Account/Login")
    });
}

private static void ConfigureWebApi(IAppBuilder app)
{
    HttpConfiguration config = new HttpConfiguration();
    WebApiConfig.Register(config);
    app.UseWebApi(config);
}

private static void ConfigureSimpleinjector(Container container)
{
    SimpleInjectorInitializer.Initialize(container);
}

И простой инициализатор инжектора выглядит примерно так:

private static void InitializeContainer(Container container)
{
    container.Register<DbContext, AngularAppContext>();

    container.Register<IUserStore<Users, Guid>, AngularAppUserStore>();
    container.Register<IRoleStore<Roles, Guid>, AngularAppRoleStore>();

    container.Register<UserManager<Users, Guid>, AngularAppUserManager>();
    container.Register<RoleManager<Roles, Guid>, AngularAppRoleManager>();
    //container.RegisterPerWebRequest<SignInManager<Users, Guid>, AngularAppSignInManager>();

    container.Register<IdentityFactoryOptions<AngularAppUserManager>, IdentityFactoryOptions<AngularAppUserManager>>();
    //container.Register<IAuthenticationManager>(() => HttpContext.Current.GetOwinContext().Authentication);

    //container.Register<SignInManager<Users, Guid>, AngularAppSignInManager>();
    // For instance:
    // container.Register<IUserRepository, SqlUserRepository>();
}

Теперь проблема в том, что контроллер не может зарегистрировать IAuthenticationManager. Я попытался использовать

container.Register<IAuthenticationManager>(
    () => HttpContext.Current.GetOwinContext().Authentication);

Но это оставляет меня с Исключением как:

System.InvalidOperationException: нет owin.Environment элемент был найден в контексте.

В этой строке

container.Register<IAuthenticationManager>(
    () => HttpContext.Current.GetOwinContext().Authentication);

Я также попытался вместо HttpContext.Current.GetOwinContext().Authentication использовать HttpContext.Current.GetOwinContext().Authentication с указанной выше конфигурацией в методе public void Configure(app) для регистрации с помощью app.Use(). И затем позже разрешите его через контейнер, чтобы получить IAuthenticationManager. Но все возможности оставили меня неудачным.

Что мне здесь не хватает? Почему HttpContext.Current.GetOwinContext().Authentcation не удается разрешить аутентификацию из OwinContext?

И если это не так, почему же такая же конфигурация через app.Use тоже не работает?

Ответ 1

Что вы делаете с IAuthenticationManager registration работал у меня без каких-либо проблем. В какой-то момент я получал то же исключение, что и вы, но это вызвано линией с

container.Verify();

сразу после конфигурации контейнера. Он пытался создать все экземпляры зарегистрированных объектов, но не было HttpContext.Current present, поэтому исключение.

Вы не получаете какие-либо экземпляры из контейнера до того, как будет доступен HTTP-запрос? Если вы действительно в них нуждаетесь, тогда единственный способ обойти это - использовать Factory, как было предложено NightOwl888. Если вам не нужен контейнер перед HTTP-запросом, тогда рефакторинг, так что он не использует outtith HTTP-запрос.

Ответ 2

Как уже упоминалось ранее в TrailMax, исключение, которое вы получили, вероятно, поднялось во время вызова container.Verify(). При запуске приложения нет HttpContext, поэтому исключение.

Хотя удаление вызова container.Verify() "решит" проблему, я бы посоветовал не делать этого, и я предлагаю лучшее решение ниже.

NightOwl888 ссылается на старую статью Марка Сееманна (которую я очень уважаю за его работу над DI). В этой статье Марк объясняет, почему он считает, что проверка контейнера бесполезна. Эта статья, однако, кажется устаревшей, и конфликты с новыми статьями от Марка. В более новой статье Марк объясняет, что одним из больших преимуществ использования Pure DI (то есть инъекции зависимостей без с использованием контейнера DI) заключается в том, что обеспечивает самую быструю обратную связь о правильности, которую вы можете получить. Марк и остальные из нас, очевидно, оценивают как обратную связь компилятора, так и обратную связь от инструментов анализа статического кода, как механизм быстрой обратной связи. И простой инжектор .Verify(), и Диагностические службы пытаются вернуть эту быструю обратную связь. На мой взгляд, метод Simple Injector .Verify() принимает на себя задание, которое компилятор сделает для вас, когда Pure DI и диагностические службы являются в некотором смысле статическим инструментом анализа кода, специализированным для вашей конфигурации DI.

Хотя для контейнера действительно невозможно выполнить 100% -ную проверку его конфигурации, проверка по-прежнему оказалась для меня ценной практикой. Было бы глупо думать, что простой вызов .Verify() приведет к полной ошибке или даже к рабочему приложению. Если кто-то может подумать, что это то, что означает "проверка вашей конфигурации DI", я понимаю, почему они утверждают, что эта функциональность бесполезна. Похоже на утверждение трюизма. Там нет контейнера, в том числе Simple Injector, который претендует на такую ​​функцию.

Вы все еще несете ответственность за запись интеграционных и/или модульных тестов, например. обнаружение правильности порядка применения декораторов или если все реализации ISomeService<T> действительно зарегистрированы в контейнере.

Я хочу упомянуть 2 конкретных аргумента из блога Marks против проверки контейнера.

Легко понять, что контейнер проверяет, но все же ломается во время выполнения.

Я согласен с этим, но я думаю, что документация Simple Injector получила несколько отличных рекомендаций относительно того, как подойти к этому здесь.

При выполнении соглашения по конфигурации легко получить регистрацию, которая не должна быть в контейнере.

У меня никогда не было этой проблемы, потому что я считаю, что разумная практика не позволяет попасть в эту ситуацию в любом случае.

Вернуться к вопросу:

Хотя один из советов в документации Simple Injector - использовать абстрактные фабрики, я бы этого не делал. Создание factory для того, что уже существует, звучит довольно странно для меня. Может быть, это просто проблема правильного именования, но зачем AccountController нужен AuthenticationFactory или AuthenticationContext? Другими словами, почему приложение должно знать что-либо о том, что у нас возникают проблемы с подключением к сети из-за некоторых дизайнерских причуд в ASP.NET Identity?

Вместо этого, регулируя регистрацию для IAuthenticationManager, мы можем вернуть компонент аутентификации из вновь созданного OwinContext при запуске/проверке времени и вернуть "нормальный" или настроенный AuthenticationManager во время выполнения. Это устранит необходимость в factory и перенесет ответственность за корневой состав, где он должен быть. И позволяет вам вставлять IAuthenticationManager всюду, в которой вы нуждаетесь, но все же можете позвонить на .Verify().

Код выглядит так:

container.RegisterPerWebRequest<IAuthenticationManager>(() => 
    AdvancedExtensions.IsVerifying(container) 
        ? new OwinContext(new Dictionary<string, object>()).Authentication 
        : HttpContext.Current.GetOwinContext().Authentication); 

Еще большее решение SOLID, однако, должно было бы не зависеть от IAuthenticationManager вообще, потому что в зависимости от этого интерфейса мы вынуждены нарушать Принцип разделения сегрегации, что затрудняет создание для него реализации прокси-сервера, что задерживает создание,

Вы можете сделать это, указав абстракцию, которая соответствует вашим потребностям и только вашим потребностям. Если посмотреть на шаблон шаблона идентификатора IAuthenticationManager, для этой абстракции потребуется не больше методов .SignIn() и .SignOut(). Это, однако, заставит вас полностью реорганизовать crappy AccountController, который вы получили "бесплатно" с помощью шаблона Visual Studio, что может быть довольно серьезным делом.

Ответ 3

Смотрите мой ответ здесь.

Хотя вы обращаетесь к другому типу, проблема одна и та же. Вы не можете полагаться на свойства HttpContext во время запуска приложения, потому что приложение инициализируется вне контекста пользователя. Решение состоит в том, чтобы сделать абстрактное factory для чтения значений во время выполнения, а не при создании объекта, и ввести в ваш контроллер factory, а не тип IAuthenticationManager.

public class AccountController
{
    private readonly AngularAppUserManager _userManager;
    private readonly AngularAppSignInManager _signInManager;
    private readonly IAuthenticationManagerFactory _authenticationManagerFactory;

    public AccountController(AngularAppUserManager userManager
      , AngularAppSignInManager signinManager
      , IAuthenticationManagerFactory authenticationManagerFactory)
    {
        this._userManager = userManager;
        this._signInManager = signinManager;
        this._authenticationManagerFactory = authenticationManagerFactory;
    }

    private IAuthenticationManager AuthenticationManager
    {
        get { return this._authenticationManagerFactory.Create(); }
    }

    private void DoSomething()
    {
        // Now it is safe to call into HTTP context
        var manager = this.AuthenticationManger;
    }
}

public interface IAuthenticationMangerFactory
{
    IAuthenticationManger Create();
}

public class AuthenticationMangerFactory
{
    public IAuthenticationManger Create()
    {
        HttpContext.Current.GetOwinContext().Authentication;
    }
}

// And register your factory...
container.Register<IAuthenticationManagerFactory, AuthenticationMangerFactory>();