In .NET 6, I couldn't find a way to affect the order in which the hosted services are started, so I had to find another way.
Luckily, there's IHostApplicationLifetime
which lets you hook into the ApplicationStarted
lifecycle event.
Oddly, ApplicationStarted
is a CancellationToken
, and not a C# event.
You can use the Register
method on the cancellation token to run code when the web application has started.
At this point, the URLs will be populated on the IServer
object.
Here's an example with 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;
}
When you run the application, the output will look like this:
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\
As you can see by the output, there's no address on IServer
when the hosted service is started, but then the server starts, and the ApplicationStarted
cancellation token is cancelled, which triggers the callback and now there are URLs on IServer
.
In my case, I actually needed it for BackgroundService
in which case it works better IMO, because you can create your own task and await
it. Here's an example:
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;
}
}
The result would be the same as the hosted service, but you do have to use BackgroundService
and ExecuteAsync
which may not make sense for your use case.