不能在没有被实例化两次的情况下在 ConfigureServices 中使用注册的单例

pcd*_*dev 5 c# singleton dependency-injection service-locator asp.net-core-2.0

我有一个 .Net Core 项目,它注册了许多单例,如下所示:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMemoryCache();
    services.AddLogging();

    services.AddSingleton<IConfiguration>(Configuration);
    services.AddSingleton<IDbFactory, DefaultDbFactory>();
    services.AddSingleton<IUserRepository, UserRepository>();    
    services.AddSingleton<IEmailService, EmailService>();          
    services.AddSingleton<IHostedService, BackgroundService>();
    services.AddSingleton<ISettingsRepository, SettingsRepository>();
    services.AddSingleton(typeof(TokenManager));

    var sp = services.BuildServiceProvider();
    var userRepository = sp.GetService<IUserRepository>();
    // ...
}
Run Code Online (Sandbox Code Playgroud)

这是注册这些类的唯一地方,在任何地方都没有创建其他实例,但是我注意到构造函数被调用了两次。这是为什么?

pcd*_*dev 5

TL;DR - 如果您需要将您注册的单身人士之一作为配置选项传递给AddMvc,请不要做我所做的并GetServiceConfigureServices方法中使用。跳到下面的编辑 2。

我发现这个答案到类似的问题,但得到的答复是不清楚相对于服务注册的顺序,但它确实点出该呼叫services.BuildServiceProvider()建立这会导致重新注册该服务的新容器中。事后看来有点道理......

编辑:

我最初的解决方法是services.BuildServiceProvider()AddSingleton注册之前移动,但事实证明这并不总是有效,正如佛巴迪指出的那样。

我刚刚看到这个问题,它提供了更多关于正在发生的事情的细节。原始注册并没有像我想的那样被丢弃。我在想这一切都错了。

调用services.BuildServiceProvider()确实构建了一个新的服务提供者/容器,但这与注册无关。实现类IUserRepository和所有依赖服务的类在被调用时由新的服务提供者实例化sp.GetService<IUserRepository>(),但原始服务IServiceProvider仍然存在,这就是应用程序的其余部分将使用的。

因此,当应用程序需要 的实例IUserRepository(比如说因为它是 的注入依赖项UsersController,现在已被请求)时,IUserRepository服务(及其依赖项,如果有)将再次实例化,因为它现在位于原始IServiceProvider.

在回答上述规定,就可以防止这一点,并使用一个问题的评论IServiceProvider“从回国服务提供者的实例ConfigureServices方法,以便将容器应用程序使用以及”。我不确定如果没有存储指向它的类变量,你会怎么做,所以它可以Configure通过设置在方法中换出app.ApplicationServices- 但这对我也不起作用,因为新的服务提供者缺少所有MVC 服务。

有些人建议使用app.ApplicationServicesinConfigure()来访问所需的服务,但这对我不起作用,因为我需要ConfigureServices按如下方式使用它:

//...

serviceProvider = services.BuildServiceProvider();
var userRepository = serviceProvider.GetService<IUserRepository>();

// Add framework services
services.AddMvc(

    config =>
    {
        var policy = new AuthorizationPolicyBuilder()
                        .RequireAuthenticatedUser()
                        .Build();
        config.Filters.Add(new RolesAuthorizationFilter(userRepository));
    });
Run Code Online (Sandbox Code Playgroud)

编辑2:

找到了解决办法! 这篇精彩的帖子描述了我想要实现的目标,并提出了一个非常整洁的解决方案。

如果您需要在 MVC 配置选项中传递对已注册单例之一的引用,而不是尝试在内部实例化它ConfigureServices,您可以简单地创建一个新类,该类实现IConfigureOptions<MvcOptions>了可以接受注入的依赖项,将此类注册为单例 -和其他一切都得到了照顾 - 太棒了!

例子:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMemoryCache();
    services.AddLogging();

    services.AddSingleton<IConfiguration>(Configuration);
    services.AddSingleton<IDbFactory, DefaultDbFactory>();
    services.AddSingleton<IUserRepository, UserRepository>();    
    services.AddSingleton<IEmailService, EmailService>();          
    services.AddSingleton<IHostedService, BackgroundService>();
    services.AddSingleton<ISettingsRepository, SettingsRepository>();
    services.AddSingleton(typeof(TokenManager));

    // var sp = services.BuildServiceProvider(); // NO NEED for this after all
    // var userRepository = sp.GetService<IUserRepository>();

    services.AddMvc(); // No config options required any more
    services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();  // Here be the magic...

    // ...
}
Run Code Online (Sandbox Code Playgroud)

然后创建新类:

public class ConfigureMvcOptions : IConfigureOptions<MvcOptions>
{
    private readonly IUserRepository userRepository;
    public ConfigureMvcOptions(IUserRepository userRepository)
    {
        this.userRepository = userRepository;
    }

    public void Configure(MvcOptions options)
    {
        var policy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .Build();
        options.Filters.Add(new RolesAuthorizationFilter(userRepository));
    }
}
Run Code Online (Sandbox Code Playgroud)

多亏了 DI 的魔力,其他一切都得到了照顾。我的用户存储库单例及其依赖项仅实例化一次。

  • 在注册之前将 BuildServiceProvider 移至对我不起作用。我收到“没有注册‘IMySingleton’类型的服务。” 例外。相反,我使用 app.ApplicationServices.GetRequiredService&lt;IMySingleton&gt;() 将单例初始化移动到 Startup.Configure(),现在我没有创建多个单例实例。就我而言,我有一个很长的运行需要立即启动的操作,而不是等待有人调用我的服务,并且我希望该服务在此过程完成之前不可用。 (2认同)