I have a working Blazor app that reads and writes to CosmosDb. Trying to add some new functionality, which works fine locally, but bombs after deploying to Azure. I've traced the problem to somewhere in my CosmosDbService
. I want to add ILogger
into CosmosDbService
and further trace down my problem. However, while testing the DI, my code stalls at CreateDatabaseIfNotExistsAsync(databasename)
. No errors in the prior code. No exceptions, no timeouts, nothing just a silent fail. The Azure is not reporting anything. If I roll everything back to original state, it works. Obviously it is there somewhere, but I am not seeing it. So my questions are 1) Why does my app now stall/hang? 2) Am I injecting ILogger correctly into my CosmosDbService? Here is the relevant code.
Startup.cs
....
services.AddSingleton<ICosmosDbService>((s) =>
{
var logger = s.GetRequiredService<ILogger<CosmosDbService>>();
return InitializeCosmosClientInstanceAsync(Configuration.GetSection("CosmosDb"), logger).GetAwaiter().GetResult();
});
'''
private static async Task<CosmosDbService> InitializeCosmosClientInstanceAsync(IConfigurationSection configurationSection, ILogger logger)
{
string databaseName = configurationSection.GetSection("DatabaseName").Value;
string containerRatings = configurationSection.GetSection("ContainerRatings").Value;
string containerUsers = configurationSection.GetSection("ContainerUsers").Value;
string containerLocations = configurationSection.GetSection("ContainerLocations").Value;
string account = configurationSection.GetSection("Account").Value;
string key = configurationSection.GetSection("Key").Value;
CosmosClientBuilder clientBuilder = new CosmosClientBuilder(account, key);
CosmosClient client = clientBuilder
.WithConnectionModeDirect()
.WithSerializerOptions(new CosmosSerializationOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default })
.Build();
//CosmosDbService cosmosDbService = new CosmosDbService(client, databaseName, containerRatings, containerUsers, containerLocations);
CosmosDbService cosmosDbService = new CosmosDbService()
{
ContainerRatings = client.GetContainer(databaseName, containerRatings),
ContainerRateLocationSmall = client.GetContainer(databaseName, containerLocations),
ContainerRateUser = client.GetContainer(databaseName, containerUsers),
CosmosClient = client,
DatabaseName = databaseName
, _logger = logger
};
DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerRatings, "/What");
await database.Database.CreateContainerIfNotExistsAsync(containerUsers, "/UserName");
await database.Database.CreateContainerIfNotExistsAsync(containerLocations, "/id");
return cosmosDbService;
}
...
CosmosDBService.cs
...
public class CosmosDbService : ICosmosDbService
{
public CosmosClient CosmosClient { get; set; }
public string DatabaseName { get; set; }
public Container ContainerRatings { get; set; }
public Container ContainerRateUser { get; set; }
public Container ContainerRateLocationSmall { get; set; }
public ILogger _logger { get; set; }
//public CosmosDbService() { }
...
Thanks in advance.
--UPDATE 2020-12-06 ---
Thank you Quango. Your questions and suggestions stimulated a new line of thinking. I'm giving you credit for the answer.
- I noticed that the new line in
Startup.cs
was causing the service to lazy load much later in the actual program execution:
var logger = s.GetRequiredService<ILogger<CosmosDbService>>();
When I returned this line to its original syntax, it initialized during Startup successfully.
services.AddSingleton<ICosmosDbService>(InitializeCosmosClientInstanceAsync(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
- Getting ILogger injected into my CosmosDBService. Doing re-RTFM on Logging (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0) it seemed I needed to use the Factory instead. So I changed the instantiation to this:
LoggerFactory loggerFactory = new LoggerFactory();
//CosmosDbService cosmosDbService = new CosmosDbService(client, databaseName, containerR8ings, containerUsers, containerLocations);
CosmosDbService cosmosDbService = new CosmosDbService()
{
ContainerR8ings = dbClient.GetContainer(databaseName, containerR8ings),
ContainerR8User = dbClient.GetContainer(databaseName, containerUsers),
ContainerR8LocationSmall = dbClient.GetContainer(databaseName, containerLocations),
_logger = loggerFactory.CreateLogger("CosmosDbService")
};
And correspondingly to the CosmosDBService by removing the explicit constructor and using the implicit one. Then I was able to add Logger to the service.
- Finally this enabled me to track the original bug I was hunting.
Thanks again for the assistance.
As you've seen you cannot asynchronously initialize a service in the service registration process. The solution I use in these cases is not to create the service directly, but return a factory or provider service which has an
async Task<T> CreateAsync
method to do this. You register this provider as the service, then inject this provider and callCreateAsync
in the code. Here is an example:I changed your config code to use
GetValue<T>
- I think it's simpler, and added a bunch of logging statements. Mostly these areLogDebug
so in production you'll need to change to debug-level (or you can change toLogInformation
if you wish) to help diagnose the issue.In
Startup.cs
we add:To get the CosmosDB instance you inject the provider using DI. In controllers, Razor and Blazor pages you can then make
async
calls: e.g. a sample Blazor pageCosmos.razor