使用Simple Injector注册IAuthenticationManager

时间:2014-12-12 16:19:19

标签: c# asp.net-mvc asp.net-identity asp.net-identity-2 simple-injector

我正在为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的相同配置也不起作用?

3 个答案:

答案 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需要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); 

然而,更加强大的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>();