8

On the IServiceCollection the provided methods for registring services AddTransiet, AddScoped, AddSingleton do not allow you the use of async-await construct when you have to retrieve a service by computing some of its steps.

I want to know if making an async version would be a valid approach.

internal static IServiceCollection AddScopedResolveAsync<TService>(this IServiceCollection serviceCollection, Func<IServiceProvider, Task<TService>> func)
    => serviceCollection.AddScoped(func);

and then use it

services.AddScopedResolveAsync<IMyService>(async serviceProvider =>
{
    await something;

    return new MyService();
});
  • 6
    Inject a factory service instead that *can* create instances asynchronously. I would definitely not register Tasks into DI – pinkfloydx33 Feb 21 '21 at 10:54

2 Answers2

1

Question is indeed interesting, especially for transient and scoped services. For singletons, I've solved this issue in a couple of projects by completing async initialization in Program#Main like this:

await webHost.Services.GetService<IMyService>().InitializeAsync();

I have tested some approaches of your case with scoped services. As @pinkfloydx33 points out, injecting Task<IMyService> is not a very clean approach, mainly because container issues are brought into multiple implementations, but also because it will make writing tests harder.

Another issue we have to take into account, is disposal of instances when not having the container taking care of that.

I would probably start off with a simple as possible solution like below, and advance if performance issues.

services.AddScoped<IMyService>(serviceProvider =>
{
    var someResultFromAsyncMethod = serviceProvider.GetService<IAnotherService>().AnAsyncMethod()
       .GetAwaiter()
       .GetResult();
    
    ...
    
    return new MyService(...);
});

In this answer, all container async issues are moved into service class: https://stackoverflow.com/a/43240576/14072498

But again, it gets complicated, and the price for keeping methods async, may be steep.

Roar S.
  • 8,103
  • 1
  • 15
  • 37
0

What you are really registering in the container is Task<TService> with a scoped lifetime. Therefore it would be a valid approach if TService is not disposable, because the scope will not dispose TService when the scope is disposed, it will dispose Task<TService>

And to use it in another service you write something like this:

public class AnotherService
{
   private readonly Task<MyService> _myServiceTask;
   public AnotherService(Task<MyService> myServiceTask) 
   {
      this._myServiceTask = myServiceTask;
   }

   public async Task DoSomethingAsync() 
   {
       var myService = await _myServiceTask;
       ......
   }

}
Jesús López
  • 8,338
  • 7
  • 40
  • 66