.NET Core 3.0 IHostedService 访问 Web 服务器 URL - 方案、主机、端口等

.NET Core 3.0 IHostedService access web server URL - scheme, host, port etc

我遇到的问题很简单。我想在我的 IHostedService.

中访问服务器 URL

我找不到办法。那里没有任何请求,所以我不能使用 IHttpContextAccessor.

IServer 功能 属性 没有任何地址,我没有选择。

我不想在配置中对服务器的 URL 进行硬编码。

我运行的.NET core版本是3.0.

您可以使用 Startup.cs 中的依赖注入框架注册您的托管服务:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IHostedService, MyHostedService>();
}

然后,您可以将 IServer 注入托管服务并使用 IServerAddressesFeature:

获取地址
public class MyHostedService : IHostedService
{
    private readonly IServer _server;

    public MyHostedService(IServer server)
    {
       _server = server;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
       var features = _server.Features;
       var addresses = features.Get<IServerAddressesFeature>();
       var address = addresses.Addresses.FirstOrDefault(); // = http://localhost:5000
    }
}

在完全启动服务器之前,您无法从服务器获取 address/url。

如果您需要本地主机地址,请从 ./Properties/launchSettings.json 中查找(以编程方式或其他方式)。

如果您在现场环境中需要地址,则必须等待服务器加载。

对于 Asp.Net Core 3.1,您无法在 IHostedService 中从 IServerAddressesFeature 获取地址,因为 IHostedServiceServer 之前启动。看到这个 issue.

What has changed is the ordering of IHostedService's being called. This was moved up in 3.0, but still runs after Configure.

要在您的 IHostedService 中获取地址,您可以将它们设为 运行 在 Startup 之后。我发现其中一种方法是在 Program.cs 中注册您的服务,详情请参阅 here

public class Program
{
    public static void Main(string[] args)
        => CreateHostBuilder(args).Build().Run();

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder => // The GenericWebHostSevice is registered here
            {
                webBuilder.UseStartup<Startup>();
            })
            // Register your HostedService AFTER ConfigureWebHostDefaults
            .ConfigureServices(
                services => services.AddHostedService<ProgramHostedService>());
}

在 .NET 6 中,我找不到影响托管服务启动顺序的方法,所以我不得不寻找另一种方法。 幸运的是,有 IHostApplicationLifetime 可以让您连接到 ApplicationStarted 生命周期事件。 奇怪的是,ApplicationStarted 是一个 CancellationToken,而不是 C# 事件。 当 Web 应用程序启动时,您可以在取消令牌上使用 Register 方法来 运行 代码。 此时,URL 将填充到 IServer 对象上。

这里有一个 IHostedService 的例子:

using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;

public class MyHostedService : IHostedService
{
    private readonly IServer _server;
    private readonly IHostApplicationLifetime _hostApplicationLifetime;

    public MyHostedService(IServer server, IHostApplicationLifetime hostApplicationLifetime)
    {
        _server = server;
        _hostApplicationLifetime = hostApplicationLifetime;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine($"Address before application has started: {GetAddress()}");

        _hostApplicationLifetime.ApplicationStarted.Register(
                () => Console.WriteLine($"Address after application has started: {GetAddress()}"));
        
        return Task.CompletedTask;
    }

    private string GetAddress()
    {
        var features = _server.Features;
        var addresses = features.Get<IServerAddressesFeature>();
        var address = addresses.Addresses.FirstOrDefault();
        return address;
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

当您 运行 应用程序时,输出将如下所示:

Address before application has started:
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7012
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5012
Address after application has started: https://localhost:7012
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Users\niels\RiderProjects\UrlsInHostedService\UrlsInHostedService\

从输出中可以看出,托管服务启动时 IServer 上没有地址,但随后服务器启动,ApplicationStarted 取消令牌被取消,这触发了回调,现在 IServer.

上有 URL

就我而言,我实际上 BackgroundService 需要它,在这种情况下,IMO 效果更好,因为您可以创建自己的任务并 await 它。这是一个例子:

using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;

public class MyBackgroundService : BackgroundService
{
    private readonly IServer _server;
    private readonly IHostApplicationLifetime _hostApplicationLifetime;

    public MyBackgroundService(IServer server, IHostApplicationLifetime hostApplicationLifetime)
    {
        _server = server;
        _hostApplicationLifetime = hostApplicationLifetime;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        Console.WriteLine($"Address before application has started: {GetAddress()}");

        await WaitForApplicationStarted();
        
        Console.WriteLine($"Address after application has started: {GetAddress()}");
    }

    private string GetAddress()
    {
        var features = _server.Features;
        var addresses = features.Get<IServerAddressesFeature>();
        var address = addresses.Addresses.FirstOrDefault();
        return address;
    }    
    
    private Task WaitForApplicationStarted()
    {
        var completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        _hostApplicationLifetime.ApplicationStarted.Register(() => completionSource.TrySetResult());
        return completionSource.Task;
    }
}

结果与托管服务相同,但您必须使用 BackgroundServiceExecuteAsync,这对您的用例可能没有意义。