31

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..

HotN
  • 3,939
  • 3
  • 34
  • 47
diegosasw
  • 8,724
  • 7
  • 66
  • 106
  • 6
    Just a quick note that in AspNetCore there is a service that can be injected in any class to get the base url from the current request (in case we're not in a controller with direct access to Request). Simply by injecting `IHttpContextAccessor contextAccessor` and then building the `var url = $"{contextAccessor.HttpContext.Request.Scheme}://{contextAccessor.HttpContext.Request.Host.ToUriComponent()}";` But as per https://github.com/aspnet/Hosting/issues/793 it seems it will be necessary to explicitly register it in DI: `services.AddSingleton();` – diegosasw Oct 05 '16 at 20:37

3 Answers3

16

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;

azaki
  • 178
  • 1
  • 4
  • 3
    This only works when debugging using VS. In both IIS Express and IIS, it returns random port number. – Whoever Nov 15 '17 at 21:59
  • Does it return a random port, or is it the port which kestrel is listening on? IIS routes traffic to the kestrel web service using ANCM. – ColinM Oct 11 '18 at 22:38
10

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.

poke
  • 307,619
  • 61
  • 472
  • 533
Set
  • 40,147
  • 18
  • 114
  • 133
5

,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;
        }
    }
Maxim T
  • 619
  • 6
  • 10
  • 1
    No addresses in IServerAddressesFeature for Asp.Net Core 3.1 using default web server builder locally running IIS Express, at least, even with a lauchSettings.json and an IIS Express profile – gfache Jul 23 '20 at 10:10
  • That means it's a history now – Maxim T Dec 13 '20 at 06:35