1

I've used other DI framework in the past, now I have to use the microsoft one (.NET Core 3.0) and I need to call a InitializeAsync method when the service is used (It's a singleton so I have only one instance in the whole app). I don't want to perform such operation in the Constructor sincec it has to make a call to a web api , and I also don't want to put a variable inside the method that the service implements and check if it's just initialized.

You can consider the following snippet of code

  class Program
    {
        static void Main(string[] args)
        {
            using IHost host = CreateHostBuilder(args).Build();

            ExemplifyScoping(host.Services, 1);
            ExemplifyScoping(host.Services, 88);

            host.RunAsync();
        }

        private static void ExemplifyScoping(IServiceProvider hostServices, int scope)
        {
            var service = hostServices.GetService<IDummyService>();

          var str =  service.PerfomSomething(scope);

          Console.WriteLine($"RES : {str}");

        }

        static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((_, services) =>
                {
                    services.AddTransient<IDummyService, DummyService>();

                }
                );
    }

and the simplified service class

    public class DummyService : IDummyService
    {
        private IDictionary<int, string> _dictionary;
        public Task InitializeAsync()
        {
            _dictionary = new Dictionary<int, string>();

            _dictionary.Add(1,"1");
            _dictionary.Add(2,"2");
            _dictionary.Add(3,"3");
            _dictionary.Add(4,"4");

            return  Task.CompletedTask;
        }

        public string PerfomSomething(int id)
        {
            if (_dictionary.ContainsKey(id))
                return _dictionary[id];

            return string.Empty;
        }
    }

    public interface IDummyService
    {
        Task InitializeAsync();

        string PerfomSomething(int id);
    }

I've seen that the DI framework has a PostConfigure method but I don't know if it's what I need to use.

Any advice? Thanks

advapi
  • 3,661
  • 4
  • 38
  • 73

2 Answers2

2

The short answer is that you don't call the InitializeAsync method using the container. Instead you invoke it manually from within your Composition Root.

Letting your DI container invoke an async method (or any method that performs I/O for that matter) is typically a bad idea, because composing object graphs should be fast and reliable. That's why DI Containers usually not expose an ResolveAsync method, because that doesn't make sense in their context.

In your case, you need one-time application wide initialization of a component. This can be done effectively by resolving and invoking the target component after all components have been registered, and the container has been built.

Here's a simple example that demonstrates this:

static async Task Main(string[] args)
{
    using IHost host = CreateHostBuilder(args).Build();

    await host.Services.GetRequiredService<IDummyService>().InitializeAsync();

    ...

    host.RunAsync();
}

Consider removing the InitializeAsync method from the IDummyService abstraction, and make it available only to the implementation. The InitializeAsync method is an implementation detail and only the Composition Root needs to be aware of it, while the Composition Root already knows about the existence of the implementation (it registered that implementation).

For a more-detailed discussion on the topic of async initialization and DI Containers, see this q/a between Stephen Cleary, Mark Seemann, and myself.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • Hello Steven, thanks for your reply... what should be the implementation with the removed InitalizeAsync? how do you normally handle such case? since I think I'm not the only one in the world who's trying to have some init done – advapi Nov 17 '21 at 10:23
  • Hi @advapi, I'm not saying *not* to have any initialization done, but what I'm trying to say is: don't leak this implementation detail into your `IDummyService` abstraction. Just have the `DummyService` *implementation* have `InitializeAsync` as `public` method, and let your Composition Root resolve that implementation and invoke that method. – Steven Nov 17 '21 at 10:53
  • but how does the Composition Root knows to call the InitializeMethod? I mean, if I register the service as I've done, and then I remove from the Interface the InitializeAsync, leaving it on the DummyService class, when and how I call it? – advapi Nov 17 '21 at 11:23
  • In that case, you've basically got 2 options: either you resolve the interface and cast it back to the implementation, or you register both the abstraction *and* its implementation in the container and resolve the implementation in your main. In case of the latter option, take a look at [this answer](https://stackoverflow.com/a/41812930/264697) how to make such registration. – Steven Nov 17 '21 at 11:36
0

I do encourage you to call it in the constructor maybe as a parallel async. However, I guess you have your reasons to not do so. Here is how you could do it.

  1. Register the Implementation to itself

    services.AddTransient<DummyService, DummyService>();

  2. Register the Interface to a Factory Method

    services.AddSingleton<IDummyService>(sp =>
    {
        var service = sp.GetService<DummyService>();
        service.InitializeAsync();
        return service;
    });
Wahid Bitar
  • 13,776
  • 13
  • 78
  • 106
  • Your advice is, IMO, a dangerous practice, because the initialization will still run on the background when the application starts using `DummyService`. This might cause all kinds of race conditions, if not designed carefully. It also causes I/O during object graph composition, which makes composition [slow](https://blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple/) and [unreliable](https://blog.ploeh.dk/2011/03/04/Composeobjectgraphswithconfidence/). – Steven Nov 17 '21 at 20:23
  • Thank you @Steven, but that is exactly what I understood from the question. I do not recommend this pattern, but I tried to answer how to do it technically. – Wahid Bitar Nov 18 '21 at 06:19