【问题标题】:How to inject a reference to a specific IHostedService implementation?如何注入对特定 IHostedService 实现的引用?
【发布时间】:2018-12-17 15:21:18
【问题描述】:

我的网络应用有一个后台服务,它监听服务总线。基于docs,运行后台服务的内置方式似乎是实现IHostedService

所以我有一些看起来像这样的代码:

public class ServiceBusListener : IMessageSource<string>, IHostedService
{
    public virtual event ServiceBusMessageHandler<string> OnMessage = delegate { };

    public Task StartAsync(CancellationToken cancellationToken)
    {
        // run the background task...
    }

    // ... other stuff ...
}

然后在Startup.cs 中注册服务:

services.AddSingleton<IHostedService, ServiceBusListener>();

一旦我更新到 ASP.NET 2.1,我就可以使用新的便捷方法:

services.AddHostedService<ServiceBusListener>();

但我相信两者在功能上是等价的。

复杂之处:我的网络应用程序有多个IHostedService 实现(具体来说,服务总线侦听器的不同实例)。

问题:如何让其他组件获得对特定托管服务实现(我的服务总线侦听器)的引用?换句话说,如何将特定实例注入到组件中?

用例:我的后台服务侦听服务总线消息,然后将消息作为 .NET 事件重新发布(如果您想知道,消费代码会处理线程问题)。如果事件在后台服务上,那么订阅者需要获取对后台服务的引用才能订阅。

我的尝试:如果我做了显而易见的事情并将ServiceBusListener 声明为要注入到不同组件中的依赖项,我的启动代码会抛出“无法解析类型”异常。

甚至可以请求IHostedService 的特定实现吗?如果没有,最好的解决方法是什么?引入我的服务和消费者都可以引用的第三个组件?避开IHostedService,手动运行后台服务?

【问题讨论】:

  • 使用工厂方法并将依赖关系解析到您的类中,并在工厂方法中新建它。当它有许多构造函数参数时可能会变得乏味。或者使用第 3 方解析器。最后但并非最不重要的一点是,您可以使用 ActivatorUtils.CreateInstance 方法将依赖项替换为合适的对象(您需要在工厂方法中解决一些 where 或 new 它(取决于您的情况,哪个最适合)。docs.microsoft.com/en-us/dotnet/api/…跨度>
  • 另外 "Could not resolve a service of type" exception 那是因为你没有注册它:P 你只注册了 IHostedService => ServiceBusListener,没有 ServiceBusListener =>服务总线监听器
  • 为什么要获得对托管服务的引用?托管服务旨在自行在后台运行。也许您只需要创建一个接口并注册一个单例,然后您就可以在需要的地方注入它?
  • @Steven:我喜欢这个主意。也许有一个实现 IHostedService 的启动类,它知道如何委托给我单独注册的单例(使用我的理想接口)。

标签: asp.net-core dependency-injection


【解决方案1】:

原来有一种简单的方法可以做到这一点(感谢指针,Steven)。

如果您需要能够注入/获取对某些服务的引用,请继续正常注册该服务(不用担心任何IHostedService 的东西):

services.AddSingleton<ServiceBusListener>();

现在我们可以注册一个单独的托管服务,它的唯一职责是启动/停止我们刚刚注册的服务:

services.AddHostedService<BackgroundServiceStarter<ServiceBusListener>>();

BackgroundServiceStarter 是一个看起来像这样的辅助类:

public class BackgroundServiceStarter<T> : IHostedService where T:IHostedService
{
    readonly T backgroundService;

    public BackgroundServiceStarter(T backgroundService)
    {
        this.backgroundService = backgroundService;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        return backgroundService.StartAsync(cancellationToken);
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return backgroundService.StopAsync(cancellationToken);
    }
}

2018 年 8 月 6 日更新:根据ygoe 的建议,更新了代码以避免服务定位器模式

【讨论】:

    【解决方案2】:

    根据您的回答,我做了一个有用的扩展方法。它允许使用另一个接口注册IHostedService

    其他接口不需要实现IHostedService,所以你不要暴露StartAsync()StopAsync()方法

    public static class ServiceCollectionUtils
    {
        public static void AddHostedService<TService, TImplementation>(this IServiceCollection services)
            where TService : class
            where TImplementation : class, IHostedService, TService
        {
            services.AddSingleton<TService, TImplementation>();
            services.AddHostedService<HostedServiceWrapper<TService>>();
        }
    
        private class HostedServiceWrapper<TService> : IHostedService
        {
            private readonly IHostedService _hostedService;
    
            public HostedServiceWrapper(TService hostedService)
            {
                _hostedService = (IHostedService)hostedService;
            }
    
            public Task StartAsync(CancellationToken cancellationToken)
            {
                return _hostedService.StartAsync(cancellationToken);
            }
    
            public Task StopAsync(CancellationToken cancellationToken)
            {
                return _hostedService.StopAsync(cancellationToken);
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      谢谢你的回答,迈克尔,它运作良好。奇怪的是,我们需要这样的 hack 才能使依赖注入真正起作用。

      我已经更改了您的课程,这样您就不需要服务定位器了。泛型在这里提供帮助。

      public class BackgroundServiceStarter<T> : IHostedService
          where T : IHostedService
      {
          private readonly T backgroundService;
      
          public BackgroundServiceStarter(T backgroundService)
          {
              this.backgroundService = backgroundService;
          }
      
          public Task StartAsync(CancellationToken cancellationToken)
          {
              return backgroundService.StartAsync(cancellationToken);
          }
      
          public Task StopAsync(CancellationToken cancellationToken)
          {
              return backgroundService.StopAsync(cancellationToken);
          }
      }
      

      【讨论】:

        【解决方案4】:

        .net 核心现在有一个AddHostedService 重载,可让您指定用于解析托管服务的工厂函数/方法。这使您可以将托管服务指定为单例,然后在必要时将其注入。

        文档说它从 .Net Core 3.x 开始就可以使用了

        可以说,应该分析导致这条路径的架构选择,以确保确实需要这条路径。但有时它可能是必要的。

        AddHostedService<THostedService>(IServiceCollection, Func<IServiceProvider,THostedService>)
        

        用法是

        services
           .AddSingleton<IMyServiceContract, MyServiceImplementation>()
           .AddHostedService(services => (MyServiceImplementation) services.GetService<IMyServiceContract>())
        ;
        

        如果您的 IServiceContract 继承自 IHostedService 或者您不使用接口,则不需要强制转换。

        【讨论】:

          【解决方案5】:

          从 .net core 3.1 开始,不需要其他答案的复杂性。如果您不需要从另一个类中获取对您的类的具体引用,只需调用:

          services.AddHostedService<MyHostedServiceType>();
          

          如果您必须有具体的参考,请执行以下操作:

          services.AddSingleton<IHostedService, MyHostedServiceType>();
          

          【讨论】:

          • 你的意思是我们应该调用 AddSingleton 和 AddHostedService 吗?如果有,按什么顺序?
          • 我注意到services.AddSingleton&lt;IHostedService, MyHostedServiceType&gt; 也有效。不过,不确定如果添加多个托管服务会发生什么
          • 我分享的代码只有在你需要从另一个服务中获取对该服务的引用时才需要。如果您不需要,services.AddHostedService 本身就足够了。
          • services.AddSingleton&lt;IHostedService, MyHostedServiceType&gt; 允许您从另一个服务获取对该服务的引用
          • 我有两个实例。尝试在ExecuteAsync/StartAsync 中设置一些东西,当使用IHostedService 时,找不到“一些东西”
          【解决方案6】:

          回复jjxtra的excellent answer的我的小助手代码

              /// <summary>
              /// Instead of calling service.AddHostedService<T> you call this make sure that you can also access the hosted service by interface TImplementation
              /// https://stackoverflow.com/a/64689263/619465
              /// </summary>
              /// <param name="services">The service collection</param>
              public static void AddInjectableHostedService<TService, TImplementation>(this IServiceCollection services)
                  where TService : class
                  where TImplementation : class, IHostedService, TService
              {
                  services.AddSingleton<TImplementation>();
                  services.AddSingleton<IHostedService>(provider => provider.GetRequiredService<TImplementation>());
                  services.AddSingleton<TService>(provider => provider.GetRequiredService<TImplementation>());
              }
          

          所以这将允许您注册您的 IHostedService

                  services
                      .AddInjectableHostedService<IExposeSomething, MyHostedServiceType>();
          

          MyHostedServiceType 当然会实现一些接口 (IExposeSomething),其中包含您希望向其他类型公开的内容。

          【讨论】:

            猜你喜欢
            • 2018-06-30
            • 2019-02-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-08-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多