Зарегистрировать 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>();