c# - 无法在 ConfigureServices 中使用已注册的信号,除非它们被实例化两次
问题描述
我有一个 .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>();
// ...
}
这是注册这些类的唯一地方,并且没有在任何地方创建其他实例,但是我注意到构造函数被调用了两次。这是为什么?
解决方案
TL;DR - 如果您需要将其中一个已注册的单例作为配置选项传递给AddMvc
,请不要做我所做的并GetService
在该ConfigureServices
方法中使用。跳到下面的编辑 2。
我找到了一个类似问题的答案,但答案并不清楚关于服务注册的顺序,但它确实指出调用services.BuildServiceProvider()
会构建一个新容器,导致服务重新注册。事后看来有点道理...
编辑:
我最初的解决方法是services.BuildServiceProvider()
在注册之前移动AddSingleton
,但事实证明这并不总是有效的,正如佛巴迪指出的那样。
我刚刚看到这个问题,它提供了更多关于正在发生的事情的细节。原始注册并没有像我想的那样被丢弃。我一直在想这一切都错了。
调用services.BuildServiceProvider()
确实构建了一个新的服务提供者/容器,但这与注册无关。类实现和所有依赖服务在被调用时由新的服务提供者IUserRepository
实例化,但原始的仍然存在,这就是应用程序的其余部分将使用的。sp.GetService<IUserRepository>()
IServiceProvider
因此,当应用程序IUserRepository
需要.UsersController
IUserRepository
IServiceProvider
对上述问题的回答中的一条评论指出,您可以防止这种情况并使用它IServiceProvider
,“通过从方法返回服务提供者实例,ConfigureServices
这样它也将成为您的应用程序使用的容器”。我不确定如果不存储指向它的类变量,您将如何做到这一点,因此可以Configure
通过设置在方法中将其换出app.ApplicationServices
-但这对我也不起作用,因为新的服务提供者丢失了所有MVC 服务。
有些人建议使用app.ApplicationServices
inConfigure()
来访问所需的服务,但这对我不起作用,因为我需要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));
});
编辑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...
// ...
}
然后创建新类:
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));
}
}
多亏了 DI 的魔力,其他一切都得到了照顾。我的用户存储库单例及其依赖项仅实例化一次。