传递参数给AddHostedService函数

25

我正在编写一个 .Net Core Windows 服务,下面是一小段代码:

internal static class Program
    {
        public static async Task Main(string[] args)
        {
            var isService = !(Debugger.IsAttached || args.Contains("--console"));

            var builder = new HostBuilder()
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<IntegrationService>();
                });

            if (isService)
            {
                await builder.RunAsServiceAsync();
            }
            else
            {
                await builder.RunConsoleAsync();
            }
        }
    } 

我想向我的服务IntegrationService传递一些参数-我该如何向我的服务发送参数?


您可以通过工厂方法传递。 services.AddHostedService<IntegrationService>(sp => new IntegrationService(...)); 中的 sp 代表了 IServiceProvider,您也可以使用它来获取信息以实例化托管服务。如果您需要其他选项,请考虑添加配置选项或类似选项,然后可以通过构造函数注入,这样您就不必手动执行此操作。此重载在 .net core 3 preview6 中存在,但在 .net core 2.2 中不存在。因为您没有指定使用的版本,所以我加了注释。 - Joelius
@Joelius,就像Kirk Larkin说的那样,AddHostedService没有重载。关于第二种方法,你能否在答案中详细说明一下? - Arslan Pervaiz
3
你可以模拟负载。它只是AddTransient<IHostedService, TWhatever>()的一个包装器,当然支持工厂函数方法。 - Kirk Larkin
4个回答

23

关于Joelius对.Net Core 3的答案进行了小更新

给定一个具有混合参数(TimeSpan)和服务(ILogger<StatusService>, IHttpClientFactory)的HostedService构造函数

public StatusService(
            TimeSpan cachePeriod,
            ILogger<StatusService> logger,
            IHttpClientFactory clientFactory)

您可以在Startup.cs中像这样将其添加到HostedService:

services.AddHostedService 
    (serviceProvider => 
        new StatusService(
            TimeSpan.FromDays(1), 
            serviceProvider.GetService<ILogger<StatusService>>(), 
            serviceProvider.GetService<IHttpClientFactory>()));

19

虽然上面的答案是正确的,但它们有一个缺点,就是您不能再在服务构造函数中使用DI。

我所做的是:

class Settings {
  public string Url { get; set; }
}
class SomeService : IHostedService {
  public SomeService (string instanceId, IOptionsMonitor<Settings> optionsMonitor) {
    var settings = optionsMonitor.Get(instanceId);
  }
}
services.Configure<Settings>("Instance1", (s) => s.Url = "http://google.com");
services.Configure<Settings>("Instance2", (s) => s.Url = "http://facebook.com");

services.AddSingleton<IHostedService>(x => 
 ActivatorUtilities.CreateInstance<SomeService>(x, "Instance1")
);

services.AddSingleton<IHostedService>(x => 
 ActivatorUtilities.CreateInstance<SomeService>(x, "Instance2")
);

这将为每个实例创建命名设置,并将命名设置名称传递给HostedService。 如果您想要具有相同类和不同参数的多个服务,请确保使用AddSingleton而不是AddHostedService,因为AddHostedService将仅添加相同类型的一个实例,这将导致只启动一个实例!


2
当你的类需要ILogger和IOptions等内容时,这可能是最佳解决方案。 - Lee Harrison

9

Joelius的回答是正确的,虽然还有另一种方法可以做到这一点

services.AddSingleton<IHostedService>(provider => new IntegrationService("Test"));

2
这可能会给你大部分功能,但你能确保它提供与AddHostedService完全相同的行为吗?经过一段时间查看源代码后,我无法确认这些调用是否相等 :/ - Joelius
2
是的,它提供了与“AddHostedService”相同的行为。 - Arslan Pervaiz

5
在 .net core 3 之前,您可以使用一个配置类,通过 DI 将其注入到服务中。您的配置类可能如下所示:
class IntegrationConfig
{
    public int Timeout { get; set; }
    public string Name { get; set; }
}

然后您需要将此配置添加到 DI 系统中:
services.AddSingleton(new IntegrationConfig
{
    Timeout = 1234,
    Name = "Integration name"
});

在类IntegrationService中,您需要添加一个构造函数,该构造函数接受一个配置对象作为参数:
public IntegrationService(IntegrationConfig config)
{
    // setup with config or simply store config
}

这基本上是你所需要的。在我的观点中,它不是最好的解决方案,在 .net core 3 中,你可以简单地使用工厂函数添加 HostedService,但我认为如果你使用 .net core 2.2 或以下版本,则这样做是最好的选择。
编辑:
在评论中,Kirk Larkin 提到了这一点:
“你可以模拟重载。它只是 AddTransient() 的包装,当然支持工厂函数方法。”
对于这个问题,您可能需要查看当前可访问的重载,该重载位于 此处
/// <summary>
/// Add an <see cref="IHostedService"/> registration for the given type.
/// </summary>
/// <typeparam name="THostedService">An <see cref="IHostedService"/> to register.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to register with.</param>
/// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
/// <returns>The original <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services, Func<IServiceProvider, THostedService> implementationFactory)
    where THostedService : class, IHostedService
{
    services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService>(implementationFactory));

    return services;
}

请注意,更改此文件的最新提交是在6月3日,并且已标记为 .net core 3 的预览版6和预览版7。由于我从未听说过TryAddEnumerable并且不是微软员工,我不知道你是否可以直接翻译它。
仅仅通过查看AddTransient的当前实现并深入研究几个文件,我很遗憾无法清晰地画出线条,以便能够给出你当前可以通过.net core 3获得的确切功能。
我提供的解决方法仍然有效,并且根据情况似乎是可接受的。

你好,你能说明一下在 .NET Core 3.1 中使用工厂函数的方法吗? - serge
@Serge,请参考其他答案,此答案是针对3.0之前的版本编写的。我写这个答案已经很久了,我认为应该优先考虑OP的答案。 - Joelius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接