ASP.NET Core配置DI后初始化单例

100

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

services.AddSingleton<IFoo, Foo>();

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

在我目前的理解中,直到第一次使用(询问)之前,Foo 实例才不会被创建。除了构造函数外,是否有一种初始化此类的方法?像在 ConfigureServices() 完成后立即进行初始化?还是应该将初始化代码(从数据库加载数据)放在 Foo 的构造函数中?

(如果这个类能在第一次使用之前加载其数据,以加快首次访问速度,那就太好了)


1
您可以创建 new Foo() 并在 ConfigureServices 方法中进行注册。 - adem caglin
4
那行不通,因为Foo有依赖关系,所以它没有默认构造函数。 - Cody Stott
5个回答

139

在启动期间自行操作。

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

或者"加热它"

public void Configure(IApplicationBuilder app) 
{
    app.ApplicationServices.GetService<IFoo>();
}
或者,作为替代方案。
public void Configure(IApplicationBuilder app, IFoo foo) 
{
    ...
}

但这种感觉只是肮脏的,并且更多地与你的设计问题有关,如果你在构造函数中做了不应该做的事情。类实例化必须是快速的,如果您在其中运行长时间操作,则违反了大量最佳实践,需要重构代码库而不是寻找绕过它的方法。


9
但是这样我就需要担心Foo的所有依赖关系(所有仓库及其依赖项)。这当然是可行的,但我正在寻找一种不必与系统抗争的方法。 - pbz
1
是的。通常只有需要特殊处理的类才会进行初始化,例如启动服务总线连接等,这些操作不希望在启动时间发生。不过我们需要更多信息来确定您具体尝试了什么。我怀疑您在构造函数中执行了一些长时间运行的操作,这是绝对不可取的。类实例化应该快速完成,永远不要在构造函数中执行长时间运行的操作。当然,您也可以在 Configure 中解析类,但这只是一种“肮脏”的做法。 - Tseng
5
对我来说,代码是这样的:public void Configure(IApplicationBuilder app) { app.ApplicationServices.GetService(); }其中,Foo 被更改为了 IFoo - marrrschine
42
如果您的单例服务需要从文件中读取数据或从数据库加载数据并充当缓存,该怎么办?在第一个请求期间(服务被请求时)执行此操作似乎不太合适,但对我来说,这似乎是一种常见情况。此外,这些操作可能很危险,并且可能会抛出异常,您希望捕获、记录等。如果自己实例化服务或“预热”感觉不好,那么如何做到最佳实践? - margaretkru
1
@tseng 我在 Startup 类中最终得到了以下代码:var rabbitMqManager = app.ApplicationServices.GetService<IRabbitMqManager>(); var rabbitMqReceiver = app.ApplicationServices.GetService<IRabbitMqReceiver>(); rabbitMqManager.Subscribe(rabbitMqReceiver); - Alexander
显示剩余8条评论

8

最近我一般将它创建为一个IHostedService,如果需要初始化,因为对我来说,让服务自己处理初始化似乎更合理,而不是在外部进行。

你甚至可以使用BackgroundService代替IHostedService,因为它们非常相似,只需要实现ExecuteAsync即可。

这里是它们的文档:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services

下面是如何添加该服务以便你可以直接注入它的示例:

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

一个简单服务的示例:

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;
    }
}

我后来创建了一些扩展方法,因为我需要再次使用相同的模式

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());
}

用法类似于

services.AddHostedServiceAsService<MyService>();

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

警告:上述操作将在启动期间导致无限递归。正确的方法是首先注册为单例,然后作为由工厂提供的托管服务进行注册,例如:services.AddSingleton<IMyHostedService, MyHostedService>().AddHostedService(x => x.GetRequiredService<IMyHostedService>()); - Jez
@Jez 不会的,它已经经过测试并按预期工作。我认为您可能是引入了无限递归的人,请再次检查您的代码。 - Moaaz Alkhrfan
我实际尝试了你的代码,结果出现无限递归,直到我反转了单例的注册以及托管服务。 - Jez
@Jez 无限递归并不是自发发生的,它们只会在服务之间相互依赖时发生,而这里的服务不依赖任何东西,这意味着它不可能自己创建无限递归。 - Moaaz Alkhrfan

7

4
您好,Jérôme FLAMEN,欢迎您。建议您添加更多信息,因为如果链接过时,那么您的回答质量会非常低。 - Tiago Martins Peres
谢谢提供的文章参考,但在我的Main方法中,我的WebHost是这样初始化的:CreateWebHostBuilder(args).Build().Run()。所以,我调用的是Run方法而不是StartAsync方法。我认为调用异步方法而不是同步方法并不是问题(因为我可以捕获结果的Task并等待它),但是调用Run和Start之间有什么区别吗? - Marcos Arruda

5

在Jérôme FLAMEN的回答中,他提供了我需要调用异步初始化任务到单例的关键。以下是对其回答的进一步补充:

创建一个实现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;
}

接下来,在您的ConfigureServices中,添加一个HostedService引用:

services.AddHostedService<PostStartup>();

From link.


0

我创建了一些管理器,需要订阅其他服务的事件。 我不喜欢在其中进行这个操作。

webBuilder.Configure (applicationBuilder => ...

我认为应该在这个部分。
webBuilder.ConfigureServices ((context, services) => ...

所以,这是我的答案(在 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>();

        ...
        });
  

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接