Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async provider in .NET Core DI

I'm just wondering if it's possible to have async/await during DI.

Doing the following, the DI fails to resolve my service.

services.AddScoped(async provider => 
{
  var client = new MyClient();
  await client.ConnectAsync();
  return client;
});

where as the following works perfectly fine.

services.AddScoped(provider => 
{
  var client = new MyClient();
  client.ConnectAsync().Wait();
  return client;
});
like image 998
rethabile Avatar asked Apr 05 '17 19:04

rethabile


People also ask

How does DI work in .NET Core?

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. For more information specific to dependency injection within MVC controllers, see Dependency injection into controllers in ASP.NET Core.

What is async in .NET Core?

These two keywords – async and await – play a key role in asynchronous programming in ASP.NET Core. We use the async keyword in the method declaration and its purpose is to enable the await keyword within that method.

What is IoC container in .NET Core?

ASP.NET Core contains a built-in dependency injection mechanism. In the Startup. cs file, there is a method called ConfigureServices which registers all application services in the IServiceCollection parameter. The collection is managed by the Microsoft.


1 Answers

Although it is theoretically possible to use async/await during object resolution, it doesn't make much sense when resolving dependencies, because:

  • Constructors can't be asynchronous, and
  • Construction of object graphs should be simple, reliable and fast

This means that everything that involves I/O should be postponed until after the object graph has been constructed.

So instead of injecting a connected MyClient, MyClient should connect when it is used for the first time, not when it is created.

Since your MyClient is not an application component but a third-party component, this means that you can't ensure that it "connect[s] when it is used for the first time."

This shouldn't be a problem, however, because the Dependency Inversion Principle already teaches us that:

the abstracts are owned by the upper/policy layers

This means that application components should not depend on third-party components directly, but instead they should depend on abstractions defined by the application itself. As part of the Composition Root, adapters can be written that implement these abstractions and adapt application code to the third-party libraries.

An important advantage of this is that you are in control over the API that your application components use, which is the key to success here, as it allows the connectivity issues to be hidden behind the abstraction completely.

Here's an example of how your application-tailored abstraction might look like:

public interface IMyAppService
{
    Task<Data> GetData();
    Task SendData(Data data);
}

Do note that this abstraction lacks an ConnectAsync method; this is hidden behind the abstraction. Take a look at the following adapter for instance:

public sealed class MyClientAdapter : IMyAppService, IDisposable
{
    private readonly Lazy<Task<MyClient>> connectedClient;

    public MyClientAdapter()
    {
        this.connectedClient = new Lazy<Task<MyClient>>(async () =>
        {
            var client = new MyClient();
            await client.ConnectAsync();
            return client;
        });
    }

    public async Task<Data> GetData()
    {
        var client = await this.connectedClient.Value;
        return await client.GetData();
    }

    public async Task SendData(Data data)
    {
        var client = await this.connectedClient.Value;
        await client.SendData(data);
    }

    public void Dispose()
    {
        if (this.connectedClient.IsValueCreated)
        {
            this.connectedClient.Value.Dispose();
        }
    }
}

The adapter hides the connectivity details from the application code. It wraps the creation and connection of MyClient in a Lazy<T>, which allows the client to be connected just once, independently of in which order the GetData and SendData methods are called, and how many times.

This allows you to let your application components depend on IMyAppService instead of MyClient and register the MyClientAdapter as IMyAppService with the appropriate lifestyle.

like image 187
Steven Avatar answered Oct 21 '22 15:10

Steven