5

The issue that I have is simple. I want to access the server URL in my IHostedService.

I cannot find a way to do it. There isn't any request there so I cannot use the IHttpContextAccessor.

IServer features property doesn't have any addresses and I am out of options.

I do not want to hard code the URL of the server in the configuration.

The version of .NET core that I am running is 3.0.

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
ppavlov
  • 378
  • 5
  • 14
  • 1
    I mean, you can inject `IHttpContextAccessor` into your class... If you could give more context about where/when you need the information, I would think we could help you better. – Heretic Monkey Oct 18 '19 at 19:49
  • I need it in a IHostedService, which is executed right after the start of the application. IHttpContextAccessor requires a Request and can be used in the controller. But in IHostedService I do not have a request – ppavlov Oct 18 '19 at 21:23
  • 1
    Long story short, you can't. See [How do I get the website URL or access the HttpContext from a IHostedService in ASP.NET Core?](https://stackoverflow.com/q/57004484/215552) – Heretic Monkey Oct 18 '19 at 21:42
  • Isn't there another way ? A way not using HttpContext ? – ppavlov Oct 18 '19 at 21:44

4 Answers4

4

You can register your hosted service with the dependency injection framework in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IHostedService, MyHostedService>();
}

Then, you can inject an IServer into your hosted service and get the addresses using IServerAddressesFeature:

public class MyHostedService : IHostedService
{
    private readonly IServer _server;

    public MyHostedService(IServer server)
    {
       _server = server;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
       var features = _server.Features;
       var addresses = features.Get<IServerAddressesFeature>();
       var address = addresses.Addresses.FirstOrDefault(); // = http://localhost:5000
    }
}
David James Ball
  • 903
  • 10
  • 26
  • 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
  • 1
    Addresses are empty in ASP.NET Core 6 when doing this. – Swimburger Jan 20 '22 at 00:40
1

You cannot get the address/url from your server before you completely start the server.

If you need your localhost address, look it up (programmatically or otherwise) from ./Properties/launchSettings.json.

If you need the address in a live environment, you have to wait for the server to load.

McKabue
  • 2,076
  • 1
  • 19
  • 34
1

For Asp.Net Core 3.1, you cannot get addresses from IServerAddressesFeature in IHostedService because IHostedServices are launched before Server. See this issue.

What has changed is the ordering of IHostedService's being called. This was moved up in 3.0, but still runs after Configure.

To get addresses in your IHostedServices you can make them run After Startup. I found one of the approaches is to register your service in Program.cs, see it in detail here.

public class Program
{
    public static void Main(string[] args)
        => CreateHostBuilder(args).Build().Run();

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder => // The GenericWebHostSevice is registered here
            {
                webBuilder.UseStartup<Startup>();
            })
            // Register your HostedService AFTER ConfigureWebHostDefaults
            .ConfigureServices(
                services => services.AddHostedService<ProgramHostedService>());
}
zwlxt
  • 301
  • 2
  • 9
1

In .NET 6, I couldn't find a way to affect the order in which the hosted services are started, so I had to find another way. Luckily, there's IHostApplicationLifetime which lets you hook into the ApplicationStarted lifecycle event. Oddly, ApplicationStarted is a CancellationToken, and not a C# event. You can use the Register method on the cancellation token to run code when the web application has started. At this point, the URLs will be populated on the IServer object.

Here's an example with IHostedService:

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

public class MyHostedService : IHostedService
{
    private readonly IServer _server;
    private readonly IHostApplicationLifetime _hostApplicationLifetime;

    public MyHostedService(IServer server, IHostApplicationLifetime hostApplicationLifetime)
    {
        _server = server;
        _hostApplicationLifetime = hostApplicationLifetime;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine($"Address before application has started: {GetAddress()}");

        _hostApplicationLifetime.ApplicationStarted.Register(
                () => Console.WriteLine($"Address after application has started: {GetAddress()}"));
        
        return Task.CompletedTask;
    }

    private string GetAddress()
    {
        var features = _server.Features;
        var addresses = features.Get<IServerAddressesFeature>();
        var address = addresses.Addresses.FirstOrDefault();
        return address;
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

When you run the application, the output will look like this:

Address before application has started:
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7012
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5012
Address after application has started: https://localhost:7012
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Users\niels\RiderProjects\UrlsInHostedService\UrlsInHostedService\

As you can see by the output, there's no address on IServer when the hosted service is started, but then the server starts, and the ApplicationStarted cancellation token is cancelled, which triggers the callback and now there are URLs on IServer.

In my case, I actually needed it for BackgroundService in which case it works better IMO, because you can create your own task and await it. Here's an example:

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

public class MyBackgroundService : BackgroundService
{
    private readonly IServer _server;
    private readonly IHostApplicationLifetime _hostApplicationLifetime;

    public MyBackgroundService(IServer server, IHostApplicationLifetime hostApplicationLifetime)
    {
        _server = server;
        _hostApplicationLifetime = hostApplicationLifetime;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        Console.WriteLine($"Address before application has started: {GetAddress()}");

        await WaitForApplicationStarted();
        
        Console.WriteLine($"Address after application has started: {GetAddress()}");
    }

    private string GetAddress()
    {
        var features = _server.Features;
        var addresses = features.Get<IServerAddressesFeature>();
        var address = addresses.Addresses.FirstOrDefault();
        return address;
    }    
    
    private Task WaitForApplicationStarted()
    {
        var completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        _hostApplicationLifetime.ApplicationStarted.Register(() => completionSource.TrySetResult());
        return completionSource.Task;
    }
}

The result would be the same as the hosted service, but you do have to use BackgroundService and ExecuteAsync which may not make sense for your use case.

Swimburger
  • 6,681
  • 6
  • 36
  • 63