0

I'm using the .NET Core's ServiceCollection and ServiceProvider for dependency injection in a Xamarin Forms app. I have singleton instances which do a lot of slow running work in their constructor / Init method. The problem is that the provider creates the singleton instances when they are used at the first time. I want to override this functionality in order to do the instantiation of these classes when I register them. For example my code:

services.AddSingleton<ILocalizationService, LocalizationService>();

I tried the following but it not works if the class needs instances via constructor injection (because in that scenario in need to pass them manually):

services.AddSingleton<ILocalizationService>(new LocalizationService());

Can I override the basic functionality somehow without any workaround like instantiate all of them when I build my provider?

szg1993
  • 23
  • 8

1 Answers1

2

There is no way to override or disable that behavior in MS.DI and to my knowledge, neither is such feature available in any other DI Container in the .NET ecosystem.

What you can do, however, is resolve that instance immediately after the Service Provider was built:

provider.GetRequiredService<ILocalizationService>();

This constructs that instance and runs its constructor.

Some DI Container provider a 'validation' mechanism that can test all registrations by creating them. There exists, however, no such feature in MS.DI.

But instead of having this "slow running work" in the class's constructor, I would suggest moving the initialization out of the constructor, and into an Init method of some sort, because Injection Constructors should be simple and should do no work other than storing its dependencies. Also note that if you want make this heavy operation asynchronous in the future, you will be certainly out of luck, because this is impossible to make constructors asynchronous (see this, this, and this, for more information on this).

Instead, move the logic out of the constructor into an Init method of some sort, e.g:

public class LocalizationService : ILocalizationService
{
    private readonly IDependency1 dep1;
    private readonly IDependency2 dep2;

    public LocalizationService(IDependency1 dep1, IDependency2 dep2)
    {
        // Just pre-condition checks and storing dependencies.
        // Never use injected dependencies inside the constructor.
        this.dep1 = dep1 ?? throw new ArgumentNullException();
        this.dep2 = dep2 ?? throw new ArgumentNullException();
    }

    public void Init() => /* Do heavy initialization here */

    public Task InitAsync() => /* Or do async initialization here */
}

This allows you to resolve LocalizationService from the ServiceProvider at startsup and call its Init method:

var service = provider.GetRequiredService<LocalizationService>();

// Forces initialization at startup
service.Init();
// or
await service.InitAsync();

This requires LocalizationService to be registered as well, next to its ILocalizationService interface:

services.AddSingleton<LocalizationService>();
services.AddSingleton<ILocalizationService>(c =>
    c.GetRequiredService<LocalizationService>>();

This last registration is a bit sneaky, but it allows both registrations for ILocalizationService and LocalizationService to point at the exact same instance.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • Thank you for your answer. I figured out that I can resolve the instance right after the injection, but it's not an elegant solution I think. I my case I won nothing if I move the logic from the ctor into an Init method, because I have to call the Init right after the resolving. I will mark your answer as a right answer, but guys let me know if there are an eager loading solution. :) – szg1993 Apr 14 '22 at 13:15
  • I urge you to read all referenced Q&As and articles I presented in my answer, as they do more thoroughly describe why you shouldn't pursuit a path of having any heavy initialization inside your constructor at all. – Steven Apr 14 '22 at 21:09
  • I read the article, but calling the Init and InitAsync method manully (as the article describes) cause a problem. If your instance is a singleton, and you inject it to for example 4-5 other classes, then where are you calling the Init? You are unable to know what will be the first class who will use the singleton (because it depends on the user). – szg1993 Apr 19 '22 at 09:00
  • And why does this matter from which context Init is called and which article are you referring to exactly? – Steven Apr 19 '22 at 09:42
  • I'm refering this article what you linked: https://blog.stephencleary.com/2013/01/async-oop-2-constructors.html When he calls this code: var myInstance = new MyFundamentalType(); await myInstance.Initialization; On the other hand, you also do this in your answer: await service.InitAsync(); I'm unable to determine which ViewModel needs to call the InitAsync() of my singleton service, because there are 4 of them what uses the instance. – szg1993 Apr 21 '22 at 07:47
  • Ah, I see what you mean. The answer is that you should *never* call init from within your View Models, because initialization is an implementation detail. There are multiple options here. If you really need the initialization to be done at startup, you call Init from within your Composition Root. If you can go with lazy initialization, you make sure that the type is initialized by itself on first use. That means that a methods on your singleton internally check if initialization has occurred, and if not, call `await this.InitAsync()` internally. – Steven Apr 21 '22 at 07:56
  • Okay, now I understand, thank you! :) – szg1993 Apr 22 '22 at 07:59