使用Simple Injector注册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(() => context); await next(); }); _container.Register( () => _container.GetInstance().Authentication); _container.Register<SignInManager, 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); } 

而Simple Injector Initializer看起来像这样

 private static void InitializeContainer(Container container) { container.Register(); container.Register<IUserStore, AngularAppUserStore>(); container.Register<IRoleStore, AngularAppRoleStore>(); container.Register<UserManager, AngularAppUserManager>(); container.Register<RoleManager, AngularAppRoleManager>(); //container.RegisterPerWebRequest<SignInManager, AngularAppSignInManager>(); container.Register<IdentityFactoryOptions, IdentityFactoryOptions>(); //container.Register(() => HttpContext.Current.GetOwinContext().Authentication); //container.Register<SignInManager, AngularAppSignInManager>(); // For instance: // container.Register(); } 

现在的问题是控制器无法注册IAuthenticationManager 。 我试过用

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

但这让我有exception:

System.InvalidOperationException:在上下文中找不到owin.Environment项。

在这一行

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

我也试过而不是使用HttpContext.Current.GetOwinContext().Authentication使用上面提到的配置进行HttpContext.Current.GetOwinContext().Authentication使用app.Use()注册public void Configure(app)方法。 然后通过容器解析它以获取IAuthenticationManager 。 但是每一种可能性都让我失败了。

我在这里想念的是什么? 为什么HttpContext.Current.GetOwinContext().Authentcation无法解决OwinContext的认证问题?

如果不是这样,为什么通过app.Use相同的配置也无法正常工作?

您在IAuthenticationManager注册中所做的工作对我没有任何问题。 在某些时候,我得到了与你相同的exception,但这是由线路引起的

 container.Verify(); 

就在容器配置之后。 它试图创建已注册对象的所有实例,但是没有HttpContext.Current存在,因此exception。

在任何HTTP请求可用之前,您是否没有从容器中获取任何实例? 如果你确实需要它们,那么解决这个问题的唯一方法就是使用Factory,如NightOwl888所建议的那样。 如果你在HTTP请求之前不需要容器,那么重构,所以它不能用于HTTP请求。

正如TrailMax已经提到的那样,在调用container.Verify()期间可能会引发exception。 在应用程序启动时,没有HttpContext ,因此是例外。

虽然删除对container.Verify()的调用将“解决”问题,但我建议不要这样做,我会在下面建议一个更好的解决方案。

NightOwl888引用了Mark Seemann的一篇旧文章(我非常尊重他在DI方面的工作)。 在那篇文章中, Mark解释了为什么他认为validation容器是无用的。 然而,这篇文章似乎已经过时,并与Mark的新文章发生冲突。 在一篇较新的文章中, Mark解释说使用Pure DI (即不使用DI容器的dependency injection)的一大优势是它提供了关于正确性的最快反馈 。 Mark和其他人明显重视编译器的反馈和静态代码分析工具的反馈,作为快速反馈机制。 Simple Injector的.Verify()和Diagnostic Services都试图将这种快速反馈带回来。 在我看来,Simple Injector的.Verify()方法承担了编译器在执行Pure DI时所做的工作,而诊断服务在某种意义上是专门用于DI配置的静态代码分析工具。

虽然容器确实不可能对其配置进行100%validation,但validation仍然certificate对我来说是一种有价值的做法。 认为对.Verify()的简单调用会导致完全无错误甚至是工作应用程序,这将是愚蠢的。 如果有人认为这是“validation”你的DI配置意味着什么,我理解为什么他们会争辩说这个function毫无价值。 听起来像是一个不言而喻的陈述。 那里没有容器,包括Simple Injector,它假装有这样的function。

您当然还不负责编写集成和/或unit testing,例如检测应用装饰器的顺序是否正确,或者ISomeService所有实现是否确实已在容器中注册。

我想提一下Mark的博客中有关validation容器的两个具体论点。

很容易进入容器validation的情况,但仍然在运行时中断。

我同意这一点,但我确实认为Simple Injector文档在这里如何处理这个问题有一些很好的指导。

在通过配置进行约定时,很容易获得不应该在容器中的注册。

我从来没有遇到过这个问题,因为我认为无论如何都要防止陷入这种情况是一种理智的做法。

回到问题:

虽然Simple Injector文档中的一个提示是使用抽象工厂,但在这种情况下我不会这样做。 为已经存在的东西创建一个工厂,对我来说听起来很奇怪。 也许这只是一个正确命名的问题,但为什么AccountController需要AuthenticationFactoryAuthenticationContext ? 换句话说,由于ASP.NET身份中存在一些设计怪癖,为什么应用程序应该知道有关我们有问题接线的事情?

相反,通过调整IAuthenticationManager的注册,我们可以在启动/validation时从新创建的OwinContext返回Authentication组件,并在运行时返回“normal”或配置的AuthenticationManager 。 这将消除对工厂的需求,并将责任移至应该的组合根。 并且允许您在需要的任何地方注入IAuthenticationManager ,同时仍然能够调用.Verify()

代码如下:

 container.RegisterPerWebRequest(() => AdvancedExtensions.IsVerifying(container) ? new OwinContext(new Dictionary()).Authentication : HttpContext.Current.GetOwinContext().Authentication); 

然而,更加可靠的解决方案是完全不依赖于IAuthenticationManager ,因为依赖于此接口会导致我们违反接口隔离原则,因此很难为其创建延迟创建的代理实现。

您可以通过定义符合您需求的抽象来满足您的需求。 查看身份模板对IAuthenticationManager调用,这种抽象只需要.SignIn().SignOut()方法。 然而,这将迫使您完全重构您通过Visual Studio模板“免费”获得的糟糕的AccountController ,这可能是一项艰巨的任务。

在这里看到我的答案。

虽然您正在访问其他类型,但问题是相同的。 在应用程序启动期间,您不能依赖HttpContext的属性,因为应用程序是在用户上下文之外初始化的。 解决方案是使抽象工厂在运行时而不是在创建对象时读取值,并将工厂而不是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();