我正在为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);
}
Simple Injector Initializer看起来像这样
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
方法中提到的配置public void Configure(app)
来使用app.Use()
进行注册。然后通过容器解析它以获取IAuthenticationManager
。但是每一种可能性都让我失败了。
我在这里缺少什么?为什么HttpContext.Current.GetOwinContext().Authentcation
无法解决OwinContext的认证问题?
如果不是这样,为什么通过app.Use
的相同配置也不起作用?
答案 0 :(得分:26)
正如TrailMax已经提到的那样,在调用container.Verify()
期间可能会引发异常。在应用程序启动时,没有HttpContext
,因此例外。
虽然取消对container.Verify()
的调用将“解决”问题,但我建议不要这样做,我会在下面建议更好的解决方案。
NightOwl888引用了Mark Seemann的一篇旧文章(我非常尊重他在DI方面的工作)。在that article Mark解释了为什么他认为验证容器是无用的。然而,这篇文章似乎已经过时,并与Mark的新文章发生冲突。在a newer article Mark解释说使用Pure DI(即不使用DI容器的依赖注入)的一大优势是it provides the fastest feedback about correctness that you can get。 Mark和我们其他人明显重视编译器的反馈和静态代码分析工具的反馈,作为快速反馈机制。 Simple Injector的.Verify()
和Diagnostic Services都试图将此快速反馈带回来。在我看来,Simple Injector的.Verify()
方法承担了编译器在执行Pure DI时所做的工作,而诊断服务在某种意义上是专门用于DI配置的静态代码分析工具。
虽然容器确实不可能对其配置进行100%验证,但验证仍然证明对我来说是一种有价值的做法。认为对.Verify()
的简单调用会导致完全无错误甚至是工作应用程序是愚蠢的。如果有人认为这是“验证”您的DI配置意味着什么,我理解为什么他们会争辩说这个功能毫无价值。听起来像是一个不言自明的陈述。那里没有容器,包括Simple Injector,它假装有这样的功能。
您当然不负责编写集成和/或单元测试,例如:检测应用装饰器的顺序是否正确,或者ISomeService<T>
的所有实现是否确实已在容器中注册。
我想提一下Mark的博客中有关验证容器的两个具体论点。
容易进入容器验证的情况,但在运行时仍然会中断。
我同意这一点,但我确实认为Simple Injector文档对如何处理这个问题有了很好的指导here。
在通过配置进行约定时,很容易获得不应该在容器中的注册。
我从来没有遇到过这个问题,因为我认为无论如何都要防止陷入这种情况是一种理智的做法。
回到问题:
虽然Simple Injector文档中的一个提示是使用抽象工厂,但在这种情况下我不会这样做。为已经存在的东西创建工厂,对我来说听起来很奇怪。也许这只是一个正确命名的问题,但为什么AccountController需要AuthenticationFactory
或AuthenticationContext
?换句话说,由于ASP.NET身份中存在一些设计怪癖,为什么应用程序应该知道有关我们有问题接线的事情?
相反,通过调整IAuthenticationManager
的注册,我们可以在启动/验证时从新创建的OwinContext返回Authentication组件,并在运行时返回“normal”或已配置的AuthenticationManager
。这将消除对工厂的需求,并将责任移至应该的组合根。并允许您在所需的任何位置注入IAuthenticationManager
,同时仍然可以拨打.Verify()
。
代码如下:
container.RegisterPerWebRequest<IAuthenticationManager>(() =>
AdvancedExtensions.IsVerifying(container)
? new OwinContext(new Dictionary<string, object>()).Authentication
: HttpContext.Current.GetOwinContext().Authentication);
然而,更加强大的SOLID解决方案将完全不依赖于IAuthenticationManager
,因为依赖于此接口会导致我们违反接口隔离原则,因此很难为其创建延迟的代理实现创作。
您可以通过定义符合您需求的抽象来满足您的需求。查看身份模板对IAuthenticationManager
的调用,这种抽象只需要.SignIn()
和.SignOut()
方法。然而,这将迫使您完全重构Visual Studio模板中“免费”获得的蹩脚AccountController
,这可能是一项艰巨的任务。
答案 1 :(得分:8)
您在使用IAuthenticationManager
注册worked for me时所做的事情没有任何问题。在某些时候,我得到了与你相同的异常,但这是由
container.Verify();
在容器配置之后。它试图创建已注册对象的所有实例,但没有HttpContext.Current
存在,因此例外。
在任何HTTP请求可用之前,您是否没有从容器中获取任何实例?如果你真的需要它们,那么解决这个问题的唯一方法就是使用Factory,如NightOwl888所建议的那样。如果你在HTTP请求之前不需要容器,那么重构,所以它不能用于HTTP请求。
答案 2 :(得分:4)
请参阅我的回答here。
虽然您正在访问其他类型,但问题是相同的。在应用程序启动期间,您不能依赖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<IAuthenticationManagerFactory, AuthenticationMangerFactory>();