使用Simple Injector注册IAuthenticationManager

Joy*_*Joy 12 c# asp.net-mvc simple-injector asp.net-identity asp.net-identity-2

我正在为Simple Injector配置设置,我已将所有注册移动到OWIN管道.

现在问题是我有一个控制器AccountController,实际上参数为

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

现在我的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);
}
Run Code Online (Sandbox Code Playgroud)

而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>();
}
Run Code Online (Sandbox Code Playgroud)

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

container.Register<IAuthenticationManager>(
    () => HttpContext.Current.GetOwinContext().Authentication);
Run Code Online (Sandbox Code Playgroud)

但这让我有异常:

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

在这一行

container.Register<IAuthenticationManager>(
    () => HttpContext.Current.GetOwinContext().Authentication);
Run Code Online (Sandbox Code Playgroud)

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

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

如果不是这样,为什么相同的配置app.Use也不起作用?

Ric*_*Net 26

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

虽然取消呼叫container.Verify()将"解决"问题,但我建议不要这样做,我会在下面建议一个更好的解决方案.

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

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

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

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

很容易进入容器验证的情况,但仍然在运行时中断.

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

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

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

回到问题:

虽然Simple Injector文档中的一个提示是使用抽象工厂,但在这种情况下我不会这样做.为已经存在的东西创建工厂,对我来说听起来很奇怪.也许这只是一个正确命名的问题,但为什么AccountController需要一个AuthenticationFactoryAuthenticationContext?换句话说,由于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); 
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这是最好的答案! (2认同)
  • "通过Visual Studio模板重构你'免费获得'的糟糕`AccountController`,<=这句话.让我的一天. (2认同)

tra*_*max 8

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

container.Verify();
Run Code Online (Sandbox Code Playgroud)

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

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