You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

依赖注入IOC容器注册前异步初始化的最佳实践问题求教

我搜索时使用的关键词可能有误,一直未找到匹配我需求的可靠指导方案。目前所有关于DI注册的指导都采用类似如下写法:

builder.Services.AddSingleton<MyService>(() => new MyService("connectionString"));

但这种方式过于简单,不符合实际生产场景需求。我不会将各类凭据存储在应用代码中,而是存放在Azure Key Vault这类服务中,或者使用托管身份认证,由托管身份自行获取连接字符串和密钥。
这就产生了优先获取凭据/连接字符串的需求,而这类操作现在基本仅提供异步接口,由此带来我经常遇到的问题:目前没有异步注册服务的机制。
我可以先注册一个服务,由它通过异步方法获取并返回凭据,但这样所有下游服务都需要知道要调用这个异步方法才能使用凭据,我无法在DI注册环节将这个逻辑抽象隐藏。
我也可以用.Result或者Wait()同步等待异步操作完成,但大量可靠技术指导都指出这种做法存在死锁风险,且我的代码可能会被带UI的应用引用的类库使用,因此该方案不可行。
因此我的问题是:当我无法同步提供凭据时,该如何注册对应的服务?

实际场景示例

比如我有一个Web应用需要通过托管身份访问Cosmos DB,我需要存储Cosmos DB实例的相关配置,因此依赖IConfiguration,同时希望使用单例HttpClient来获取所需的密钥。
我打算把Cosmos DB客户端的初始化逻辑封装到独立服务中,让下游代码仅需注入CosmosClient即可使用,对应的类实现如下:

public class CosmosKeyService
{
  private readonly MyCosmosOptions _cosmosOptions;
  private readonly HttpClient _http;

  public CosmosKeyService(IOptions<MyCosmosOptions> options, HttpClient http)
  {
    _cosmosOptions = options.Value;
    _http = http;
  }

  private async Task<string> GetCosmosKey()
  {
    // 实现逻辑省略
    //...
    var keys = await result.Content.ReadFromJsonAsync<CosmosKeys>();
    return keys.PrimaryMasterKey;
  }

  public async Task<CosmosClient> GetCosmosClient()
  {
     var key = await GetCosmosKey();
     return new CosmosClient(_cosmosOptions.CosmosDbEndpoint, key);
  }
}

为了支持这个类的DI注入,我做了如下注册:

builder.Services.Configure<MyCosmosOptions>(builder.Configuration.GetSection("cosmosdb"));
builder.Services.AddSingleton<HttpClient>();

之后我需要注册这个服务:

builder.Services.AddSingleton<CosmosKeyService>();

但我还希望把该服务生成的CosmosClient注册到容器中,这就是我找不到最优方案的点:

  • 我无法从builder中获取CosmosKeyService的实例,因为此时容器还未构建,而容器构建完成后就不能再注册新服务了。
  • 注册逻辑本身不支持异步方法,否则我可以直接这么写:
builder.Services.AddSingleton<CosmosClient>(async services => {
  var keyService = services.GetService<CosmosKeyService>();
  return await keyService.GetCosmosClient();
});

这样下游服务就可以直接在构造函数中注入CosmosClient使用。

  • 下游服务可以直接注入CosmosKeyService,但这样所有使用者都需要「记住」先调用初始化方法来获取CosmosClient才能使用。我更希望这个逻辑在注册环节处理,一是可以将初始化逻辑隐藏并集中管理,二是保证CosmosClient是真正的单例,而非每次使用都生成新实例。
  • 我可以再创建一个中间服务注入这个密钥解析服务来获取密钥,但它同样需要提供异步方法来获取密钥,因为我没法在注册环节隐藏这个初始化逻辑(因为不支持异步注册)。
    比如我可以创建如下服务:
public class CosmosBuilder
{
  private readonly CosmosKeyService _keySvc;
  public CosmosBuilder(CosmosKeyService keySvc)
  {
    _keySvc = keySvc;
  }

  public async Task<CosmosClient> GetCosmosClient() 
  {
    return await _keySvc.GetCosmosClient();
  }
}

但这种方式最终还是需要下游服务注入这个服务并调用初始化方法,如果必须这么做,我还不如直接让下游注入CosmosKeyService然后调用对应方法。
我理想的方案是可以在注册环节隐藏所有异步初始化逻辑,下游使用者直接注入CosmosClient就可以正常使用,但我不知道该如何实现。

针对评论的补充说明

我不想在一个4年前的回答下评论,但是我认为那个被采纳的回答存在以下问题:

将[初始化]逻辑移到组合根(Composition Root)中,这样你就可以在注册服务之前创建并初始化这些类,然后把初始化完成的实例注册到容器中。

这个方案听起来没问题,但存在两个限制:

  • 容器只能「构建」一次,我不能先构建容器,用已经注册的服务完成初始化,再追加注册更多服务供后续使用。
  • 在我上面的示例中,我明确用到了ASP.NET Core本身注册到DI中的服务(即IConfiguration),所以我除了通过DI之外没有其他方式可以访问这些服务,结合第一条限制,我没法先完成初始化再补充注册其他实现。

内容的提问来源于stack exchange,提问作者Whit Waldo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 19:54:03