0

我有一个可以读取和写入 CosmosDb 的 Blazor 应用程序。尝试添加一些新功能,这些功能在本地运行良好,但在部署到 Azure 后就崩溃了。我已将问题追溯到我的CosmosDbService. 我想添加ILoggerCosmosDbService进一步追踪我的问题。但是,在测试 DI 时,我的代码停在CreateDatabaseIfNotExistsAsync(databasename). 之前的代码没有错误。没有例外,没有超时,没有什么只是无声的失败。Azure 没有报告任何内容。如果我将所有内容都回滚到原始状态,它就会起作用。显然它在某个地方,但我没有看到它。所以我的问题是 1)为什么我的应用程序现在停止/挂起?2) 我是否将 ILogger 正确注入到我的 CosmosDbService 中?这是相关的代码。

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() { }
...

提前致谢。

--更新 2020-12-06 ---

谢谢广哥。您的问题和建议激发了新的思路。我要感谢你的答案。

  1. 我注意到新行Startup.cs导致服务在实际程序执行的后期延迟加载:
                var logger = s.GetRequiredService<ILogger<CosmosDbService>>();

当我将此行恢复为其原始语法时,它在启动期间成功初始化。

            services.AddSingleton<ICosmosDbService>(InitializeCosmosClientInstanceAsync(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
  1. 将 ILogger 注入到我的 CosmosDBService 中。在日志记录( https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0 )上进行重新 RTFM似乎我需要改用工厂。所以我将实例化更改为:
            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")               
            };

并通过删除显式构造函数并使用隐式构造函数与 CosmosDBService 相对应。然后我能够将 Logger 添加到服务中。

  1. 最后,这使我能够跟踪我正在寻找的原始错误。

再次感谢您的帮助。

4

1 回答 1

1

如您所见,您不能在服务注册过程中异步初始化服务。我在这些情况下使用的解决方案不是直接创建服务,而是返回具有async Task<T> CreateAsync执行此操作的方法的工厂或提供者服务。您将此提供程序注册为服务,然后注入此提供程序并调用CreateAsync代码。这是一个例子:

using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Fluent;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace TestWebsite.Services
{
    public class CosmosDbProvider
    {
        /// <summary>
        /// Ctor: values provided by DI
        /// </summary>
        /// <param name="configuration"></param>
        /// <param name="logger"></param>
        public CosmosDbProvider(IConfiguration configuration, ILogger logger)
        {
            config = configuration;
            log = logger;
        }

        IConfiguration config;
        ILogger log;

        ICosmosDbService cachedService;

        public async Task<ICosmosDbService> CreateAsync()
        {
            // use a cached instance for all requests (similar to Lazy<T> but async)
            if (cachedService == null)
            {
                cachedService = await InitializeCosmosClientInstanceAsync();
            }
            return cachedService;
        }

        private async Task<CosmosDbService> InitializeCosmosClientInstanceAsync()
        {
            try
            {
                log.LogInformation("InitializeCosmosClientInstanceAsync started");

                // I'd recommend GetValue<T> for reading single values over GetSection
                string databaseName = config.GetValue<string>("DatabaseName");
                string containerRatings = config.GetValue<string>("ContainerRatings");
                string containerUsers = config.GetValue<string>("ContainerUsers");
                string containerLocations = config.GetValue<string>("ContainerLocations");

                string account = config.GetValue<string>("Account");
                string key = config.GetValue<string>("Key");

                log.LogDebug($"databaseName: {databaseName}");
                log.LogDebug($"containerRatings: {containerRatings}");
                log.LogDebug($"containerUsers: {containerUsers}");
                log.LogDebug($"containerLocations: {containerLocations}");
                // might not want to log sensitive info..
                // log.LogDebug($"account: {account}");
                // log.LogDebug($"key: {key}");

                log.LogDebug("Creating CosmosClientBuilder");
                CosmosClientBuilder clientBuilder = new CosmosClientBuilder(account, key);
                
                log.LogDebug("Creating CosmosClient");
                CosmosClient client = clientBuilder
                                    .WithConnectionModeDirect()
                                    .WithSerializerOptions(new CosmosSerializationOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default })
                                    .Build();

                log.LogDebug("Creating CosmosDbService");
                CosmosDbService cosmosDbService = new CosmosDbService()
                {
                    ContainerRatings = client.GetContainer(databaseName, containerRatings),
                    ContainerRateLocationSmall = client.GetContainer(databaseName, containerLocations),
                    ContainerRateUser = client.GetContainer(databaseName, containerUsers),
                    CosmosClient = client,
                    DatabaseName = databaseName,
                    _logger = log
                };

                log.LogDebug($"CreateDatabase for '{databaseName}'");
                DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
                
                log.LogDebug($"CreateContainer for '{containerRatings}'");
                await database.Database.CreateContainerIfNotExistsAsync(containerRatings, "/What");
                
                log.LogDebug($"CreateContainer for '{containerUsers}'");
                await database.Database.CreateContainerIfNotExistsAsync(containerUsers, "/UserName");

                log.LogDebug($"CreateContainer for '{containerLocations}'");
                await database.Database.CreateContainerIfNotExistsAsync(containerLocations, "/id");

                log.LogInformation("InitializeCosmosClientInstanceAsync completed");
                return cosmosDbService;

            }
            catch (Exception e)
            {
                log.LogError(e, "Failed when initializing CosmosDB");
                throw;
            }

        }
    }
}

我更改了要使用的配置代码GetValue<T>- 我认为它更简单,并添加了一堆日志记录语句。大多数情况下,这些都是LogDebug在生产中,您需要更改为调试级别(或者LogInformation如果您愿意,您可以更改为)以帮助诊断问题。

Startup.cs我们添加:

services.AddSingleton<CosmosDbProvider>();

要获取 CosmosDB 实例,请使用 DI 注入提供程序。然后,您可以在控制器、Razor 和 Blazor 页面中进行async调用:例如,示例 Blazor 页面Cosmos.razor

@page "/cosmos"
@inject TestWebsite.Services.CosmosDbProvider cosmosProvider;

<h3>Cosmos</h3>

@if(_db !=null)
{
    <p>Your database is called @_db.DatabaseName</p>
}

@code {

    TestWebsite.Services.ICosmosDbService _db;

    protected override async Task OnInitializedAsync()
    {
        // create/get CosmosDbService
        _db = await cosmosProvider.CreateAsync();
    }
}
于 2020-12-04T09:41:35.300 回答