How to get base url without accessing a request

32,178

Solution 1

You are right, hosting URL is an external information, and you can simply pass it as configuration parameter to your application.

Maybe this will help you somehow: without request, you can get a configured listening address (like http://+:5000) using the IWebHostBuilder interface. It provides access to host settings via the GetSetting method:

/// <summary>
/// Get the setting value from the configuration.
/// </summary>
/// <param name="key">The key of the setting to look up.</param>
/// <returns>The value the setting currently contains.</returns>
string GetSetting(string key);

There is a WebHostDefaults.ServerUrlsKey setting name, that allows to configure listening address. We override it when add .UseUrls extension method:

public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls);

or define urls configuration parameter as described in the documentation (you know, by default listening is configured to localhost:5000).

So, having instance of IWebHostBuilder, you can call .GetSetting(WebHostDefaults.ServerUrlsKey) and get the current value.

Solution 2

i needed for some reason to get the base URL in Start.cs Configure, so i come up with this

var URLS = app.ServerFeatures.Get<IServerAddressesFeature>().Addresses;

Solution 3

,The ASP.NET Core Module generates a dynamic port to assign to the backend process. CreateDefaultBuilder calls the UseIISIntegration method. UseIISIntegration configures Kestrel to listen on the dynamic port at the localhost IP address (127.0.0.1). If the dynamic port is 1234, Kestrel listens at 127.0.0.1:1234. This configuration replaces other URL configurations provided by.

For IIS Integration, it works if you get the address after the WebHostBuilder.Build() have run.

 var builder = CreateWebHostBuilder(args);
 var webHost = builder.Build();
 var addresses = webHost.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
 var address = addresses.FirstOrDefault();
 AppDomain.CurrentDomain.SetData("BaseUrl", address ?? "");
 webHost.Run();

and got the local Kestrel address in the HostedService like this:

  string baseUrl = AppDomain.CurrentDomain.GetData("BaseUrl").ToString();

But there's a catch - this address is useless, because you can not make a request directly on this address. The IIS Integration middleware checks that only the IIS handler can make a request on this address. It produces a similar error:

<category>Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware</category>
  <state>'MS-ASPNETCORE-TOKEN' does not match the expected pairing token 'ed5bc610-b7b9-4c1c-9941-954d0579edfc', request rejected.</state>

And in general case (no IIS Integration) this method of getting the address does not work if you use Kestrel configured to run with a custom port (not 5000), or a dynamic port 0. In this case the address needs to be obtained in a delayed manner, only after the application started.

For this case i tried this way: In Configure method in the StartUp class, i saved in ServerAddressFeature in the private member.

  private IServerAddressesFeature _serverAddressesFeature;

  public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  {
            _serverAddressesFeature = app.ServerFeatures.Get<IServerAddressesFeature>();
          ... not related code here ...

And in the ConfigureServices method i added a dependency

 public void ConfigureServices(IServiceCollection services)
 {
     services.AddSingleton<IServerAddressesFeature>((sp) => _serverAddressesFeature);
  ... not related code here ...

Then in a hosted service i obtain this saved feature using dependency injection, and use it to get the address. It works, only get the address in the StartAsync method, not in the service constructor!

    public class WarmUpService : IHostedService
    {
        private readonly ILogger _logger;
        private readonly IServerAddressesFeature _saf;

        public WarmUpService(ILogger<WarmUpService> logger, IServerAddressesFeature serverAddressesFeature)
        {
            _logger = logger;
            _saf = serverAddressesFeature;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            try
            {
                // the URL can be Got here
                string baseUrl = _saf?.Addresses?.FirstOrDefault();
                // await _WarmUp(baseUrl);
            }
            catch(Exception ex)
            {
                _logger.LogCritical(ex, "WarmUp Failed");
            }
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            return Task.CompletedTask;
        }
    }
Share:
32,178
diegosasw
Author by

diegosasw

My name's Diego Martin, I am a Software Engineer/Architect based in Malaga, Spain. Founder of Sunny Attic Software, programming teacher and CQRS, Event Sourcing, DDD practitioner.

Updated on December 14, 2020

Comments

  • diegosasw
    diegosasw over 3 years

    How to get the base URL in AspNet core application without having a request?

    I know from the Request you can get the scheme and host (ie $"{Request.Scheme}://{Request.Host}" would give something like https://localhost:5000), but is it possible to get this information from anywhere else?

    In other words, if I have a service class that needs to build absolute URLs, how can I get the current URL when there is not an http request available?

    UPDATE: Maybe that scenario does not even make sense since the hosting URL is totally external to the application and that's why it only makes sense to extract it from the Request host..