IServerAddressesFeature addresses empty when running under dotnet.exe
Asked Answered
S

4

12

I have a ASP.NET Core 2.0 web application, where I need to pass the application's URL out during startup for the purposes of integration into something else.

When running under Visual Studio (IIS Express), IApplicationBuilder.ServerFeatures.Get<IServerAddressesFeature>().Addresses contains the URL my application is bound to (e.g. http://localhost:1234).

When run using dotnet.exe myapp.dll, the same collection is empty, however, I get a line on stdout saying Now listening on: http://localhost:5000.

The question is, to get the URL, do I have to parse the output of dotnet.exe for the line beginning Now listening on:, or is there a less fragile way?

Stace answered 31/1, 2018 at 16:24 Comment(0)
P
7

This is due to a change made to Kestrel in March 2017. From the announcement:

Hosting no longer adds default server address when no address is explicitly configured

The WebHost will no longer add the default server address of http://localhost:5000 to the IServerAddressesFeature when none is specified. The configuration of the default server address will now be a responsibility of the server.

Addresses specified in IServerAddressesFeature are intended to be used by servers as a fallback when no explicit address is specified directly.

There is an example of how to handle this in Hosting no longer adds default server address when no address is explicitly configured:

If you are implementing a server and rely on the IServerAddressesFeature to be set by hosting, that will no longer be set and a default should be added when no address is configured. As an example:

var serverFeatures = featureCollection.Get<IServerAddressesFeature>();
if (serverFeatures .Addresses.Count == 0)
{
    ListenOn(DefaultAddress); // Start the server on the default address
    serverFeatures.Addresses.Add(DefaultAddress) // Add the default address to the IServerAddressesFeature
}
Phenomenalism answered 31/1, 2018 at 17:2 Comment(1)
Hmm. So people use ServerAddressFeature to log on which endpoints did the server actually start to listen on, to which endpoints did it bind to. However in order to get his from ServerAddressFeature, we must "put it there" manually. What a huge misunderstanding on my side somewhere =) Thanks for the answer.Brand
H
2

A working solution for Kestrel can be found here: https://nodogmablog.bryanhogan.net/2022/01/programmatically-determine-what-ports-kestrel-is-running-on/

The idea is to obtain the addresses after server initialization as suggested by @Tratcher in his answer. Here is a working code sample:

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

WebApplication app = WebApplication.CreateBuilder().Build();

//app.Run();  // This starts the server and waits indefinitely. Not helpful.

app.Start();  // Start and wait for the server to be ready.

var server = app.Services.GetService<IServer>();
var addressFeature = server.Features.Get<IServerAddressesFeature>();

foreach (var address in addressFeature.Addresses)
{
    Console.WriteLine("Kestrel is listening on address: " + address);
}
    
await app.StopAsync();  // Shutdown the server.

//app.WaitForShutdown();  // Use this in production to wait indefinitely.

This displays: Kestrel is listening on address: http://localhost:5073

Honniball answered 11/11, 2022 at 7:10 Comment(0)
W
1

I accidentally found another solution that looks more async/await way, but I think it's less secure.

public static async Task Main(string[] args)
{
  var host = CreateHostBuilder(args).Build();
  var hostTask = host.RunAsync();

  var server = host.Services.GetService<IServer>();
  var addressFeature = server.Features.Get<IServerAddressesFeature>();
  var baseUrl = addressFeature.Addresses.FirstOrDefault();
 
  await hostTask;
}

Doing it in synchronous way you ensure that nobody will shutdown the host. When you call Start(), host starting all the services and returns, then our code is executed, and at the end we're waiting to the signal to stop the host.

In RunAsync() method host starting all the services in separate task, and it also calls WaitForShutdown() under the hood, so host might be immediately down during execution of our code. And the last line is an await that might get already completed task, so immediately will return.

Additionally new variable needs to be created to keep task which we will awaited at the end.

Wigwag answered 21/6 at 17:43 Comment(0)
S
0

When no url is specified the server does not choose a default until after Startup. If you're the app deployer then make sure to specify a non default url when starting the app.

Note for the IIS/Express case you're getting the private address used by the reverse proxy. There's no way to get the public address in process.

Selfannihilation answered 31/1, 2018 at 16:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.