With ASP.NET Core 3.0 I have been able to use this IHostedService
method ...
Determine port Kestrel binded to
... to determine Kestrel's dynamic port at run-time.
IServerAddressesFeature
is documented for ASP.NET 3.0 here:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.server.features.iserveraddressesfeature?view=aspnetcore-3.0
But when changing the version to ASP.NET Core 3.1 then the page redirects back to ASP.NET 3.0 with the hint that the document is not available for ASP.NET Core 3.1. Is IServerAddressesFeature
no longer working? Using IServerAddressesFeature
with ASP.NET Core 3.1 still compiles, but the port in the returned ServerAddresses
is always zero.
Program:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost().Run();
}
public static IWebHost BuildWebHost() =>
WebHost.CreateDefaultBuilder()
.UseKestrel()
.UseUrls("http://127.0.0.1:0") // port zero to use random dynamic port
.UseStartup<Startup>()
.Build();
}
Later, when Configure
is called ...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
HostedService.ServerAddresses = app.ServerFeatures.Get<Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature>();
... the server addresses are assigned to the HostedService.ServerAddresses
static variable as in the linked example. However, ServerAddresses
contain only the loopback address with port zero: "http://127.0.0.1:0"
.
Am I overlooking something? Is there a different, proper way to resolve the problem in v3.1? How do I configure Kestrel to use a random dynamic port and determine which port it is at run-time (before any controller action takes place) with ASP.NET Core 3.1?
Update
Here is an ugly workaround that helps determine the port. It appears that the timing or sequence of dynamic port allocation has changed. Returning from the HostedService.StartAsync
method and reading the server address a bit later seems to be enough. Surely there must be a better way?
public Task StartAsync(CancellationToken cancellationToken)
{
System.Threading.Tasks.Task.Run(async () =>
{
int port = 0;
while (port == 0)
{
await System.Threading.Tasks.Task.Delay(250);
var address = ServerAddresses.Addresses.FirstOrDefault();
if (string.IsNullOrEmpty(address))
continue;
// address is always in form http://127.0.0.1:port
var pos = address.LastIndexOf(':');
if (pos > 0)
{
var portString = address.Substring(pos + 1);
port = int.Parse(portString);
}
}
// have determined the dynamic port now
});
return System.Threading.Tasks.Task.CompletedTask;
}
The reason the IHostedService method does not work is because .Net Core 3 changed when IHostedServices are executed. In .Net Core 2, IHostedService was executed after the host starts so the server address information was readily available. In .Net Core 3, IHostedService runs after the host is built, but before the host starts up and the address is not yet available. This blog has a nice explanation of what changed.
The following method of getting the bound address, copied from here, works in both .Net Core 2 & 3.
You can call IWebHost.Start()
instead of IWebHost.Run()
as suggested here. This will allow execution of your Main
method to continue so you can get the desired information from IWebHost.ServerFeatures
. Just remember, your application will shutdown immediately unless you explicitly tell it not to using IWebHost.WaitForShutdown()
.
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseStartup<Startup>()
.UseUrls("http://*:0") // This enables binding to random port
.Build();
host.Start();
foreach(var address in host.ServerFeatures.Get<IServerAddressesFeature>().Addresses)
{
var uri = new Uri(address);
var port = uri.Port;
Console.WriteLine($"Bound to port: {port}");
}
//Tell the host to block the thread just as host.Run() would have.
host.WaitForShutdown();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With