Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you configure Kestrel to use a random dynamic port and determine the port at run-time with ASP.NET Core 3.1?

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;
        }
like image 267
J.R. Avatar asked Dec 06 '19 12:12

J.R.


1 Answers

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();
    }
like image 153
Preston S Avatar answered Oct 19 '22 17:10

Preston S