C# 为什么依赖解析在服务本身之后解析我的服务选项?

C# 为什么依赖解析在服务本身之后解析我的服务选项?,c#,.net-core,dependency-injection,asp.net-core-webapi,.net-core-2.2,C#,.net Core,Dependency Injection,Asp.net Core Webapi,.net Core 2.2,我有一个.NET Core 2.2 WebAPI项目,我在其中注册了三个服务,我们称它们为MailerService、TicketService和AuditServe,另外还有一个中间件例外middleware,它依赖于其中一个服务MailerService。MailerService和TicketService都依赖于强类型选项对象,我在service.Configure中注册了这些对象。我已经确保在服务之前注册了options对象,并且将options依赖项本身连接到服务的构造函数中 问题是

我有一个.NET Core 2.2 WebAPI项目,我在其中注册了三个服务,我们称它们为MailerService、TicketService和AuditServe,另外还有一个中间件例外middleware,它依赖于其中一个服务MailerService。MailerService和TicketService都依赖于强类型选项对象,我在service.Configure中注册了这些对象。我已经确保在服务之前注册了options对象,并且将options依赖项本身连接到服务的构造函数中

问题是TicketService从DI解析其options对象时很好,但出于某种原因,MailerService的配置在服务本身之后解析。下面是相关代码的草图

我设置了断点来观察解析顺序,设置MailerConfig的委托始终在MailerService构造函数之后激发。所以每次我得到MailerSerivce的实例时,它的options参数都是NULL。然而,观察TicketService的相同解析,TicketConfig在TicketService构造函数启动之前解析,TicketService获得正确配置的选项对象。除了MailerService是中间件的依赖项之外,我不知道它们之间可能有什么不同

我已经花了好几个小时研究这个问题,但是找不到任何合适的文档来解释为什么DI解析顺序可能会出问题,或者我可能在这里做错了什么。有人能猜到我可能做错了什么吗?异常中间件也需要注册为服务吗

启动

MailerService构造函数

票务构造函数

中间件构造函数


因为你所做的有点不合理

您正在注册中间件,该中间件依赖于已标记为瞬态的服务,即按需创建

但是。因此,任何依赖项也会在应用程序启动时实例化。因此,由中间件创建的临时服务的实例也是一个单实例

此外,如果您的中间件是唯一依赖于该临时服务的东西,那么将该服务注册为单例之外的任何东西都是毫无意义的

您所拥有的是依赖性生活方式不匹配。如上所述,避免这种情况的方法是确保依赖链中的所有服务都注册在同一范围内,即ExceptionMiddleware所依赖的任何服务(在本例中为AuditedMailerService)都应该是单例

如果-If-您隐式地打算或需要让AuditedMailerService是瞬态的,那么,与其将其注入中间件的构造函数中,不如:

公共例外MiddleWareRequestDelegate下一步,IOOptions配置 { _梅勒=梅勒; _anonymousUserName=config.Value.anonymousUserName; _sendToEmailAddress=config.Value.sendToEmailAddress; } 公共异步任务调用httpContext httpContext,IMailerService邮件程序 { ... } 但是,从这种生活方式不匹配的症状来看,还有一个更有趣的问题:为什么IOptions实例最终为空


我的猜测——这只是一个猜测——是您正在与ASP.NET Core 2.x的WebHost组件(运行您的web应用程序的组件)这一事实发生冲突。有一个初始的,虚拟的,是在应用程序启动的最初阶段注入服务的,然后是在应用程序的剩余生命周期中使用的真实的。链接的问题讨论了这一问题的原因:简而言之,可以获得虚拟容器注册的服务实例,然后真实容器将创建同一服务的第二个实例,从而引发问题。我相信,因为中间件在管道中运行得很早,所以它使用的IoC容器是一个不知道IOPS的虚拟容器,并且返回null。

虽然这不是一个很好的答案,但我仍然不知道为什么DI只在服务之后解决选项,我已经找到了问题的解决方案。我只是在代理中进行一次结束运行,并在我注册邮件服务的代理中显式地解析所有依赖项。我还调整了ExceptionMiddleware,将mailer服务作为InvokeAsync中的方法参数,而不是构造函数参数。服务是瞬态的或单例的并不是很重要,但目前我更喜欢瞬态

这种方法值得注意的缺点是,我不能再使用选项系统内置的实时更新机制——如果我在运行中更改appsettings中的某个值,该应用程序将需要回收以获取该值。这在我的应用程序中不是一个实际的需求,所以我可以接受它,但其他人在遵循我的方法之前应该注意

新的MailerService注册代表:

  services.AddTransient<IMailerService>(provider =>
  {
    var cfg = Configuration.GetSection("MailerSettings").Get<MailerConfig>();
    cfg.SecretKey = _GetApiKey(Configuration.GetValue<string>("MailerApiKeyFile"));

    var auditor = provider.GetService<AuditService>();

    return new AuditedMailerService(auditor, Options.Create(cfg));
  });

不确定,但如果你想分享相同的选择,为什么不只是r
把他们登记为单身。。。。这将为您省去很多麻烦。请确认该配置。GetSectionMailerSettings.Get;返回一个实际值。您似乎正在覆盖提供给代理的选项。默认情况下,它将使用任何错误,因此不会引发异常。@NKosi:GetSection调用确实在工作。下一行,获取api密钥的地方,也在工作。委托在激发时设置适当的选项对象。难题在于委托仅在AuditedMailerService的构造函数之后被激发-因此服务总是得到一个空选项对象。“我被难住了,”纳特肯尼说,“我的两个建议都没有解决这个问题。”。我尝试将AuditService和AuditedMailerService注册为Singleton,但仍然得到一个空选项对象。尝试将每个服务还原为临时服务,并将AuditMailerService注入InvokeAsync-still get null options对象。无论发生什么,它都会导致选项在AuditedMailerService之后一致解决。
public AuditedMailerService(AuditService auditRepo, IOptions<MailerConfig> opts)
{
  // always gets a NULL opts object??????
  _secretKey = opts.Value.SecretKey;
  _defaultFromAddr = opts.Value.DefaultFromAddress;
  _defaultFromName = opts.Value.DefaultFromName;
  _repo = auditRepo;
}
public TicketService(IOptions<TicketConfig> opts)
{
  // always gets an initialized opts object with proper values assigned
  ApiRoot = opts.Value.ApiRoot;
  SecretKey = opts.Value.SecretKey;
}
public ExceptionMiddleware(RequestDelegate next, IMailerService mailer, IOptions<ExceptionMiddlewareConfig> config)
{
  _mailer = mailer;
  _next = next;
  _anonymousUserName = config.Value.AnonymousUserName;
  _sendToEmailAddress = config.Value.SendToEmailAddress;
}
  services.AddTransient<IMailerService>(provider =>
  {
    var cfg = Configuration.GetSection("MailerSettings").Get<MailerConfig>();
    cfg.SecretKey = _GetApiKey(Configuration.GetValue<string>("MailerApiKeyFile"));

    var auditor = provider.GetService<AuditService>();

    return new AuditedMailerService(auditor, Options.Create(cfg));
  });