0

I want to use Cosmos DB in my ASP.net 5 WebAPI project. To create the client I want to use Dependency injection to inject it to my Service. To do so I created this statement in my public void ConfigureServices(IServiceCollection services)

services.AddSingleton(async s => {
    var dbConfig = Configuration.GetSection("Database");
    var conn = dbConfig["ConnectionString"];
    var dbName = dbConfig["DbName"];
    if (string.IsNullOrEmpty(conn))
    {
       throw new ConfigurationErrorsException();
    }
    var client = new CosmosClient(conn);
    await client.CreateDatabaseIfNotExistsAsync(dbName);
    return client;
});

But in this case I get an error in my service when I try to inject it via:

public ProductService(ILogger<ProductService> logger, CosmosClient client)
{
    this._client = client;
    _logger = logger;
}

Because my arrow function returns Task<CosmosClient> instead of CosmosClient. But how can I change that therefore Database-creation methos is async and I have to use await?!

THanks

Gerrit
  • 2,515
  • 5
  • 38
  • 63
  • I've tackled this in my own code by using a factory pattern where the create method is async, and then going via the factory from request code to get a client. The factory is non-async so resolves from DI, and then requests that need the client can asynchronously create a client and use it. – Martin Costello Apr 07 '21 at 19:28
  • Since the client is being registered as a singleton, I would suggest moving the asynchronous operation into a hosted service to be executed on startup – Nkosi Apr 07 '21 at 19:31
  • Another option would be to create an asynchronous event handler to create the database. Have a look at an example here https://stackoverflow.com/a/59474646/5233410 – Nkosi Apr 07 '21 at 19:38

1 Answers1

1

Microsoft's Dependency Injection does not support async.

I recommend having a separate "schema deployer" project that is run as part of your deployment (or locally to create databases on the emulator). Then you can remove the CreateDatabaseIfNotExistsAsync call and inject CosmosClient.

If you must use CreateDatabaseIfNotExistsAsync from within your application (and not during deployment), then you will need to inject an asynchronous factory:

public sealed class CosmosClientFactory
{
  private readonly Lazy<Task<CosmosClient>> _lazy;

  public CosmosClientFactory(Configuration configuration)
  {
    _lazy = new(async () =>
    {
      var dbConfig = configuration.GetSection("Database");
      var conn = dbConfig["ConnectionString"];
      var dbName = dbConfig["DbName"];
      if (string.IsNullOrEmpty(conn))
      {
       throw new ConfigurationErrorsException();
      }
      var client = new CosmosClient(conn);
      await client.CreateDatabaseIfNotExistsAsync(dbName);
      return client;
    });
  }

  public Task<CosmosClient> CreateAsync() => _lazy.Value;
}

Usage:

private readonly CosmosClientFactory _clientFactory;
public ProductService(ILogger<ProductService> logger, CosmosClientFactory clientFactory)
{
  _clientFactory = clientFactory;
  _logger = logger;
}
public async Task DoSomethingAsync()
{
  var client = await _clientFactory.CreateAsync();
  await client.DoSomethingAsync();
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810