NET Core 在配置 DI 后初始化单例

假设我有一个单例类实例,像这样在 DI 中注册:

services.AddSingleton<IFoo, Foo>();

假设 Foo类有许多其他依赖项(主要是允许它加载数据的存储库类)。

根据我目前的理解,Foo 实例在首次使用(请求)之前是不会创建的。除了构造函数之外,还有其他方法来初始化这个类吗?就像 ConfigureServices()完成之后?或者初始化代码(从 db 加载数据)应该在 Foo 的构造函数中完成?

(如果这个类能够在第一次使用之前加载它的数据来加速第一次访问,那就太好了)

66199 次浏览

Do it yourself during startup.

var foo = new Foo();
services.AddSingleton<IFoo>(foo);

Or "warm it up"

public void Configure(IApplicationBuilder app)
{
app.ApplicationServices.GetService<IFoo>();
}

or alternatively

public void Configure(IApplicationBuilder app, IFoo foo)
{
...
}

But this feels just dirty and is more a problem with your design, if you do something that you shouldn't in the constructor. Class instantiation has to be fast and if you do long-running operations within it, you break against a bunch of best practices and need to refactor your code base rather than looking for ways to hack around it

I got the same problem and I find Andrew Lock blog: https://andrewlock.net/running-async-tasks-on-app-startup-in-asp-net-core-3/

He explains how to do this with asp .net core 3, but he also refers to his pages on how to to this with previous version.

I made some manager and I need to subscribe to events of the other services. I didn't like doing this in the

webBuilder.Configure (applicationBuilder => ...

I think it should be in the section

webBuilder.ConfigureServices ((context, services) => ...

So, here is my answer (test on net.core 3):

public static IHostBuilder CreateHostBuilder (string [] args) =>
Host.CreateDefaultBuilder (args)
.ConfigureWebHostDefaults (webBuilder =>
{


...


services.AddSingleton<ISomeSingletonService,SomeSingletonService>();


var buildServiceProvider = services.BuildServiceProvider();
var someSingletonService = buildServiceProvider.GetRequiredService <ISomeSingletonService>();


...
});
  

Lately I've been creating it as an IHostedService if it needs initialization, because to me it seems more logical to let the initialization be handled by the service itself rather than outside of it.

You can even use a BackgroundService instead of IHostedService as it's pretty similar and it only needs the implementation of ExecuteAsync

Here's the documentation for them
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services

An example of how to add the service so you can inject it directly:

services
.AddHostedService<MyService>()
.AddSingleton<MyService>(x => x
.GetServices<IHostedService>()
.OfType<MyService>()
.First());

Example of a simple service:

public class MyService : IHostedService
{
// This function will be called automatically when the host `starts`
public async Task StartAsync(CancellationToken cancellationToken)
{
// Do initialization logic
}


// This function will be called automatically when the host `stops`
public Task StopAsync(CancellationToken cancellationToken)
{
// Do cleanup if needed


return Task.CompletedTask;
}
}

Some extension methods I created later on because i needed to use the same pattern again

public static class HostedServiceExtensions
{
public static IServiceCollection AddHostedServiceAsService<T>(this IServiceCollection services) where T : class, IHostedService
=> services.AddHostedService<T>().AddSingleton(x => x.GetServices<IHostedService>().OfType<T>().First());


public static IServiceCollection AddHostedServiceAsService<T>(this IServiceCollection services, Func<IServiceProvider, T> factory) where T : class, IHostedService
=> services.AddHostedService(factory).AddSingleton(x => x.GetServices<IHostedService>().OfType<T>().First());
}

Used like

services.AddHostedServiceAsService<MyService>();


// Or like this if you need a factory
services.AddHostedServiceAsService<MyService>(x => new MyService());

Adding detail to Jérôme FLAMEN's answer, as it provided the key I required to calling an async Initialization task to a singleton:

Create a class that implements IHostedService:

public class PostStartup : IHostedService
{
private readonly YourSingleton yourSingleton;


public PostStartup(YourSingleton _yourSingleton)
{
yourSingleton = _yourSingleton;
}


// you may wish to make use of the cancellationToken
public async Task StartAsync(CancellationToken cancellationToken)
{
await yourSingleton.Initialize();
}


// implement as you see fit
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

Then, in your ConfigureServices, add a HostedService reference:

services.AddHostedService<PostStartup>();

From link.