请记住.NET dependency injection guidelines条建议:

  • 避免使用服务定位器模式.例如,当您可以使用DI时,不要调用GetService来获取服务实例.
  • 另一个需要避免的服务定位器变体是注入在运行时解决依赖关系的工厂.这两种实践混合了控制倒置策略.

.NET文档表明我们可以通过IServiceProviderIServiceScopeFactory消费范围服务,就像herehere一样,例如

public class ConsumeScopedServiceHostedService: BackgroundService
{
   // ...
   public ScopedProcessingService(IServiceProvider services) { // ... }
   
   protected override async Task ExecuteAsync(CancellationToken stoppingToken)
   {
      using (var scope = _services.CreateScope())
      {
         var dbContext = scope.ServiceProvider.GetRequiredService<FooDbContext>();
         // ...
      }
}

另一种方式是大约variation of a factory个,例如

public class ConsumeFactoryHostedService: BackgroundService
{
   // ...
   public ConsumeFactoryHostedService(Func<FooDbContext> dbContextFactory) { // ... }

   protected override async Task ExecuteAsync(CancellationToken stoppingToken)
   {
      using (var dbContext = _dbContextFactory()) { // ... }
   }
}

但这两种实现不是都违背了前面提到的建议吗?可能对于许多情况来说"没关系"(尤其是对于"简单"的微服务),但我仍然对"最正确"的方式感兴趣-那么,我们是否应该使用另一种模式呢?How should we use short-lived 100 instances in an 101

推荐答案

但这两种实现不是都违背了前面提到的建议吗?

不,他们不是,但要理解我们必须看看Mark SeemannComposition RootService Locator模式的概念.

构图根是

是应用程序中模块组合在一起的单个逻辑位置.

在他的书Dependency Injection in .NET中,Mark将Composite Root模式描述为依赖注入的基本概念.虽然我读过这本书几遍,但我对第二版《Dependency Injection Principles, Practices, and Patterns》更熟悉,因为我是该版本的合著者.这就是为什么接下来我将仅引用该版本的内容.

在第二版中,马克和我提到了:

如果您使用DI容器,则Composite Root应该是您使用DI容器的唯一地方.在组合根之外使用DI容器会导致服务中断反模式[§4.1]

Service Locator anti-pattern是微软文档建议反对的,我们在书中也是如此.本书将服务收件箱定义为:

服务收件箱为组合根外部的应用程序组件提供对无界Volatile从属关系集的访问权限.[§5.2]

请注意回到组合根的交叉引用.这意味着上面已经提到的内容:

在组合根之外使用DI容器会导致服务中断反模式[§4.1]

使用组合根的DI容器inside很好并且不被认为是服务收件箱反模式的应用程序的原因是,在这种情况下,它不会表现出服务收件箱反模式的缺点.从某种意义上说,与使用outside Composite Root相比,应用程序的可维护性行为完全不同.或者,正如Mark Seemann过go 优雅地描述的那样:服务收件箱是关于它在应用程序中扮演的角色,而不是机制.或者,换句话说,如果您甚至无法使用DI容器inside您的Composition Root,那么您最终将根本不使用DI容器.

因此,这对于您的托管服务的特定情况意味着以下内容:

your hosted service is allows to depend on the 100 (or other container-specific interfaces)... as long as the hosted service is placed inside the Composition Root.

当然,我们可以try 将所有应用程序放入Composite Root中,但这不会导致维护地狱.组合根不应包含任何应用程序或业务逻辑;它应该仅包含 bootstrap 应用程序所需的基础设施.换句话说,允许将依赖项Bundle 在一起、管理其生命周期、定义后台系统的适配器并实现交叉关注点.

这一切意味着,如果放置在组合根中,您的托管服务应该尽可能小,并且仅处理基础设施逻辑,使其为Humble Object.您可以通过从托管服务中提取所有应用程序逻辑并将其放入单独的类中来做到这一点.该类本身可能将许多依赖项注入到其构造函数中,并且这些依赖项可以由DI容器解析.

这样,托管服务唯一要做的就是启动一个新的IServiceScope、解析提取的类、调用它并处置服务范围.例如:

// This class is part of the Composition Root
public class ConsumeScopedServiceHostedService : BackgroundService
{
    public ScopedProcessingService(IServiceProvider services) ...
   
    protected override async Task ExecuteAsync(CancellationToken token)
    {
        using (var scope = _services.CreateScope())
        {
            // ExtractClassWithTheLogic is part of the application
            var service = scope.ServiceProvider
                .GetRequiredService<ExtractClassWithTheLogic>();
         
            service.Run(token);
        }
    }
}

// Part of the application logic
public sealed class ExtractClassWithTheLogic
{
    public ExtractClassWithTheLogic(
        FooDbContext dbContext,
        /* other dependencies go here */)
    {
        ...
    }

    public void Run(CancellationToken cancellation) ...
}

Csharp相关问答推荐

.NET将复杂的Dto序列化为SON字符串

Dapper是否可以自动扩展类成员

C#使用属性和值将JSON转换为XML

通过条件列表删除/更新EF Core 7中的实体的有效方法

如何使用C#Interop EXCEL创建度量衡

如何测量在使用UTF8而不是C#中的UTF16编码字符串时内存使用量的增长

如何使用EF Core和.NET 8来upsert到具有多对多关系的表?

我如何让我的秒表保持运行场景而不重置

如何将端点(或с匹配请求并判断其路径)添加到BCL?

VS 2022 for ASP.NET Core中缺少自定义项模板

Polly重试URL复制值

我可以查看我们向应用程序洞察发送了多少数据吗?

有条件地定义预处理器指令常量

为什么我的用户界面对象移动到略低于实际目标?

为什么C#/MSBuild会自发地为不同的项目使用不同的输出路径?

序列化过程中的死循环

为什么当我try 为玩家角色设置动画时,没有从文件夹中拉出正确的图像?

如何在绑定到数据库的datagridview中向上或向下移动行

ASP.NET核心8:app.UseStaticFiles()管道执行顺序

NETSDK1201:对于面向.NET 8.0和更高版本的项目,默认情况下,指定RUNTIME标识符将不再生成自包含的应用程序