99

So let's say I have a singleton class instance that I register in the DI like this:

services.AddSingleton<IFoo, Foo>();

And let's say the Foo class has a number of other dependencies (mostly repository classes that allow it to load data).

With my current understanding, the Foo instance is not created until it's first used (asked). Is there a way to initialize this class other than the constructor? Like right after ConfigureServices() completes? Or should the initialization code (loading data from db) be done in Foo's constructor?

(It would be nice if this class could load its data before the first use to speed up first time access)

Askolein
  • 3,250
  • 3
  • 28
  • 40
pbz
  • 8,865
  • 14
  • 56
  • 70

5 Answers5

137

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

Tseng
  • 61,549
  • 15
  • 193
  • 205
  • 9
    But then I'd have to worry about all of Foo's dependencies (all the repositories and their dependencies). Definitely doable, but looking for a way that doesn't fight the system. – pbz Aug 17 '16 at 20:56
  • 1
    Yes. You usually only initialize classes that need special treatment, like starting a service bus connection etc. that you don't want happen during bootup time. We'd need more information though what exactly you try. I suspect you do some long running operation in the constructor which is an absolute no go. Class instantiation should be fast and long running operation NEVER done in constructor. Of course you can also resolve the class in `Configure` once, but that just feels "dirty" – Tseng Aug 17 '16 at 21:08
  • Yes, relatively long running because it goes to the database to retrieve data (about 50ms for multiple queries). Since this is a singleton one could argue that it doesn't matter where it's initialized. I think this is the best option so far (btw you mistyped Configure(). Just tried and it works to add IFoo to the list of params for Configure() so that way I don't have to call GetService<>(). Thanks. – pbz Aug 17 '16 at 21:17
  • @pbz: I've reverted the edit, as the edit uses an Init method on classes, which is a typical code smell, because it causes temporal coupling. Read the blog post from Mark Seemann http://blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling/ – Tseng Aug 17 '16 at 21:44
  • That's fine, but I would leave the example where you can pass IFoo as a parameter to Configure() – pbz Aug 18 '16 at 15:16
  • 5
    for me it was ```public void Configure(IApplicationBuilder app) { app.ApplicationServices.GetService(); }``` (IFoo instead of Foo) – marrrschine Jan 31 '17 at 14:29
  • 38
    What if your singleton service needs to read data from a file or load data from a database and act as cache of that data? It doesn't seem right to do it during the first request (when the service is asked for) but to me this seems like a common situation. Moreover those operations are potentially dangerous and can throw exceptions which you would want to catch, log etc. what is the best practice for doing this if instantiating service yourself or "warming up" feels dirty? – margaretkru Jul 18 '17 at 08:11
  • @margaretkru: First, you can use a [factory method](https://stackoverflow.com/questions/37507691/entity-framework-core-service-default-lifetime/37511175#37511175) or a factory class to create a DbContext which lives outside of the scoped container. If you need the singleton only for a brief moment during startup, you can create a new scope, perform your operation and dispose the scope again. See [here](https://stackoverflow.com/questions/38704025/cannot-access-a-disposed-object-in-asp-net-core-when-injecting-dbcontext/38704828#38704828) for an example of DbContext seeding – Tseng Jul 18 '17 at 09:44
  • In my case I actually need to read data from a file and initialize my singleton service with this data so I don't think I can use the examples with DbContext. I would also like to catch any exceptions that might occur during reading the data (then log them and send error to the client at subsequent requests). I would guess that the only place I can do this at startup is `Configure` method of the `Startup` class, but I still can't find a good solution. Do you know of a good and clean way to achieve this? – margaretkru Jul 18 '17 at 20:27
  • If all you need is the reading of a file, read it, initialize your singleton service and the register it with the `.AddSingleton(mySingletonInstance)`. – Tseng Jul 18 '17 at 20:37
  • @tseng in my case I have lib code which already created and added `mySingletonInstance` into services. However, I need to call additional method of it. So `.AddSingleton(mySingletonInstance)` is not the way for me... Whether using `Configure` is the only way in my case?? – Alexander Sep 17 '21 at 14:04
  • @Alexander Your library shouldn't create instances on its own, just use the container to register and create it. But even if it does, then you can use `.AddSingleton(T instance)` where you pass the existing **instance** to the method – Tseng Sep 18 '21 at 08:56
  • 1
    @tseng I ended up with the following in `Startup` class: `var rabbitMqManager = app.ApplicationServices.GetService(); var rabbitMqReceiver = app.ApplicationServices.GetService(); rabbitMqManager.Subscribe(rabbitMqReceiver);` – Alexander Sep 19 '21 at 10:27
8

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());
  • WARNING: the above will result in infinite recursion during startup. The way to do it is to register as a singleton first, then as a hosted service supplied with a factory, like: `services.AddSingleton().AddHostedService(x => x.GetRequiredService());` – Jez Feb 27 '23 at 14:59
  • @Jez No it won't, it's tested and works as intended. For all I know without you providing any context is that you're the one introducing the infinite recursion, please check your code again. – Moaaz Alkhrfan Mar 08 '23 at 15:43
  • I literally tried your code and got infinite recursion until I reversed the registration of the singleton and then the hosted service. – Jez Mar 08 '23 at 15:55
  • @Jez Infinite recursion don't just happen on their own, they only happen when services depend on each other, this this here is a service that depends on nothing which means this can't just create an infinite recursion. – Moaaz Alkhrfan Mar 17 '23 at 08:09
7

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.

Jérôme FLAMEN
  • 121
  • 1
  • 4
  • 3
    Hi Jérôme FLAMEN, welcome. Please consider adding more information because if the link becomes obsolete, then you have a very low quality answer. – Tiago Martins Peres Feb 07 '20 at 10:52
  • Thanks for the article reference, but in my Main method, my WebHost is being initialized this way: CreateWebHostBuilder(args).Build().Run(). So, I'm calling the Run method instead of the StartAsync method. I don't think calling an async method instead of a sync one is a problem (as I can just capture the resulting Task and wait on it), but is there a difference between calling Run and Start? – Marcos Arruda Sep 15 '20 at 16:11
5

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.

Mmm
  • 682
  • 9
  • 11
0

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

        ...
        });