【问题标题】:Injecting Simple Injector components into IHostedService with ASP.NET Core 2.0使用 ASP.NET Core 2.0 将 Simple Injector 组件注入 IHostedService
【发布时间】:2018-10-27 21:53:24
【问题描述】:

在 ASP.NET Core 2.0 中,有一种方法可以通过实现IHostedService 接口来添加后台任务(请参阅https://docs.microsoft.com/en-us/aspnet/core/fundamentals/hosted-services?view=aspnetcore-2.0)。按照本教程,我能够让它工作的方法是在 ASP.NET Core 容器中注册它。我的目标是从队列中读取消息并在后台处理作业;一条消息被发布到队列(通过控制器操作),然后在后台按时间间隔进行处理。

// Not registered in SimpleInjector
services.AddSingleton<IHostedService, MyTimedService>(); 

当我将此注册放入 ASP.NET Core 容器中时,它会在应用程序启动时自动启动该过程。但是,当我在 SimpleInjector 中注册它时,该服务不会自动启动。我相信是这样的,因为我们只用 MvcControllers 和 MvcViewComponents 注册了 SimpleInjector 容器:

// Wire up simple injector to the MVC components
container.RegisterMvcControllers(app);
container.RegisterMvcViewComponents(app);

我遇到的问题是当我想开始将组件注册从 SimpleInjector(例如存储库、带有装饰器的通用处理程序...)注入到 IHostedService 的实现中,如下所示:

public class TimedService : IHostedService, IDisposable
{
    private IJobRepository _repo;
    private Timer _timer;

    public TimedService(IJobRepository repo)
    {
        this._repo = repo;
    }
    ...
    ...
    ...
}

由于IHostedService 注册到 ASP.NET Core 而不是 Simple Injector,所以在运行定时后台服务时收到以下错误:

未处理的异常:System.InvalidOperationException:尝试激活“Optimization.API.BackgroundServices.TimedService”时无法解析“Optimization.Core.Interfaces.IJobRepository”类型的服务。

所以我的问题是,在 Simple Injector 中实现后台任务的最佳方式是什么?与标准 MVC 集成相比,这是否需要单独的集成包?如何将我的 Simple Injector 注册注入 IHostedService?如果我们在 Simple Injector 中注册后能自动启动服务,我想就可以解决这个问题了。

感谢您在此处提供的任何指示以及有关此主题的任何建议!我可能做错了什么。在过去的一年里,我非常喜欢使用 Simple Injector。

【问题讨论】:

    标签: c# dependency-injection asp.net-core-2.0 simple-injector


    【解决方案1】:

    我会挂接到 HostBuilder 的 ConfigureContainer 方法并在那里设置 simpleinjectore,如下所示:

                       IHostBuilder()
                       .ConfigureContainer<ServiceCollection>((builder, services) =>
                       {
                           var container = new Container();
    
                           container.RegisterSingleton<IJobRepository, JobRepository>();
                           services.AddTransient<IHostedService, TimedService>();
    
                       })
                       .ConfigureServices((hostContext, services) =>
                       {
                           // Originally we would have done this
                           //services.AddHostedService<Service>();
                       })
                       .Build();
    
            using (host)
            {
                await host.StartAsync();
                await host.WaitForShutdownAsync();
            }
    

    虽然您确实可以使用您的 IHostedService 实现,但我认为它可能会隐藏正在发生的事情。我认为基础设施引导应该在一个地方完成,或者至少在一个地方进行编排。我认为容器是基础设施,并会通过 HostBuilder 方法将其与应用程序的其余部分一起设置。

    另一个好处可能是您不会完全替换 ServiceCollection,因为它可以很好地处理其他与框架相关的事情。我仍然会使用 ServiceCollection 做的一些事情的示例:

                       IHostBuilder()
                       .ConfigureServices((hostContext, services) =>
                       {
                           services.AddLogging();
                           services.AddOptions();
                       })
    

    这与 simpleinjector 文档中关于使用 ASP.NET Core 设置容器的说明一致:

    Simple Injector的实践是使用Simple Injector构建你的应用组件的对象图,并让内置容器构建框架和第三方组件,Simple Injector的实践是使用Simple Injector构建应用组件的对象图,让内置容器构建框架和第三方组件

    这同样适用于 .net 核心和通用 HostBuilder。

    【讨论】:

      【解决方案2】:

      有多种方法可以解决这个问题。最简单的方法可能是通过内置配置系统从 Simple Injector 解析托管服务的方式交叉连接托管服务:

      // Register in Simple Injector as Singleton
      container.RegisterSingleton<THostedService>();
      
      // Cross-wire TimedService in the built-in configuration system
      services.AddSingleton<IHostedService>(
          c => container.GetInstance<TimedService>());
      

      请注意,托管服务只解析一次,并永久缓存,有效地使它们成为单例。这就是为什么你应该在 Simple Injector 中将它注册为 Singleton。

      但是,这样做的后果是您将无法将任何 ScopedTransient 依赖项注入到您的托管服务中。最重要的是,它迫使您让您的应用程序组件 (TimedService) 依赖于 ASP.NET Core 抽象 (IHostedService)。这并不理想。

      因此,我的首选方法是创建一个适配器实现,您在 ASP.NET Core 配置系统中注册该适配器实现,该系统将调用转发给 Simple Injector,同时使用特定于应用程序的抽象来实现您的服务。因此,您可以定义一个特定于您的应用程序的理想抽象,而不是创建许多 IHostedService 实现。我们称这个抽象为IMyJob

      IHostedService 适配器实现可能如下所示:

      public class SimpleInjectorJobProcessorHostedService : IHostedService, IDisposable
      {
          private readonly Container container;
          private Timer timer;
      
          public SimpleInjectorJobProcessorHostedService(Container c) => this.container = c;
      
          public Task StartAsync(CancellationToken cancellationToken)
          {
              this.timer = new Timer(this.DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
              return Task.CompletedTask;
          }
      
          private void DoWork(object state)
          {
              // Run operation in a scope
              using (AsyncScopedLifestyle.BeginScope(this.container))
              {
                  // Resolve the collection of IMyJob implementations
                  foreach (var service in this.container.GetAllInstances<IMyJob>())
                  {
                      service.DoWork();
                  }
              }
          }
      
          public Task StopAsync(CancellationToken cancellationToken)
          {
              this.timer?.Change(Timeout.Infinite, 0);
              return Task.CompletedTask;
          }
      
          public void Dispose() => this.timer?.Dispose();
      }
      

      您可以按如下方式在 ASP.NET core 中注册它:

      services.AddSingleton<IHostedService>(
          new SimpleInjectorJobProcessorHostedService(container)); 
      

      这样,您运行的实际作业可以忽略 ASP.NET Core,并且可以定义如下:

      public class CoolJob : IMyJob
      {
          private readonly IJobRepository repo;
      
          public CoolJob(IJobRepository repo) => this.repo = repo;
      
          public void DoWork() => ...
      }
      

      所有作业都可以在Simple Injector中注册如下:

       // NOTE: Simple Injector v4.3 API
      container.Collection.Register<IMyJob>(typeof(CoolJob).Assembly);
      

      【讨论】:

      • 这是一种集成所有 IHostedService 实现的巧妙方法!我真的很喜欢您将 IHostedService 实现留在适配器中的方式,我不喜欢该接口如何与我的应用程序特定代码混合在一起。我成功地实现了这一点,感谢@Steven 的快速回复。
      • 在调用异步代码时有什么想法吗?
      • @matt_lethargic:请致电GetAwaiter().GetResult()。 .NET 中的计时器不接受异步委托。
      • startup.cs中如何获取容器?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多