DEV Community

Cover image for How to get ASP.NET Core server URLs
Niels Swimburger.NET πŸ”
Niels Swimburger.NET πŸ”

Posted on • Originally published at swimburger.net on

How to get ASP.NET Core server URLs

This doesn't seem to be a very common use case, but sometimes you need access to the ASP.NET Core server URLs in your Program class. In this case, server URLs does not mean the public URLs that your users see when your website is served on the internet. These URLs are the local URLs that you specify when you run your ASP.NET Core application.

There are many ways to configure the URLs ASP.NET Core will try binding to. Here's a couple of ways:

  • You can programmatically configure the URLs using the UseUrls method.
  • You can use the --urls argument like this dotnet run --urls "http://localhost:8080".
  • You can configure the URL with the ASPNETCORE_URLS environment variable.
  • Out of the box, the web templates will create a JSON file Properties/launchSettings.json which holds multiple profiles to run your application. In that JSON file, you can change the applicationUrl property to configure which URL ASP.NET Core will bind to. There are also other ways to set the URLs using JSON configuration.

You could try to programmatically check each one of these variations to see which URLs will be used, but there are multiple built-in APIs you can use to get these URLs.

How to get ASP.NET Core server URLs in Program.cs with minimal APIs

Here's how you could access the URLs in .NET 6's minimal API Program.cs file:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

Console.WriteLine($"Urls from Program.cs before app.StartAsync(): {string.Join(", ", app.Urls)}");

await app.StartAsync();

Console.WriteLine($"Urls from Program.cs after app.StartAsync(): {string.Join(", ", app.Urls)}");

await app.WaitForShutdownAsync();
Enter fullscreen mode Exit fullscreen mode

Here's what the output looks like:

Urls from Program.cs before app.StartAsync():
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7227
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5227
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
Urls from Program.cs after app.StartAsync(): https://localhost:7227, http://localhost:5227
Enter fullscreen mode Exit fullscreen mode

You can get the URLs via the app.Urls property, but only after the application has been started.

From the output, you can see that the URLs collection is empty before the app is started, but once the app is started, the URLs collection is populated.

How to get ASP.NET Core server URLs using Dependency Injection

You can also get the URLs in any class configured with dependency injection. Here's an example using a Controller:

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

public class HomeController : Controller
{
    private readonly IServer server;

    public HomeController(IServer server)
    {
        this.server = server;
    }

    public IActionResult Index()
    {
        var addresses = server.Features.Get<IServerAddressesFeature>().Addresses;
        Console.WriteLine($"Addresses from HomeController.Index: {string.Join(", ", addresses)}");
        return View();
    }
}
Enter fullscreen mode Exit fullscreen mode

You can have the IServer object injected through your constructor, and then get the IServerAddressesFeature feature.

This IServerAddressesFeature has an Addresses property which is a collection of the ASP.NET Core URLs. This is actually what app.Urls uses in the previous example.

Any time the Index action is called, the following line is written to the console:

Addresses from HomeController.Index: https://localhost:7187, http://localhost:5187
Enter fullscreen mode Exit fullscreen mode

The Addresses collection will be empty if the server application hasn't started yet. You don't have to worry about that inside of a Controller because the controllers won't be invoked until the server has been started yet. But if you try to get address outside of Razor Pages, MVC controllers, or endpoints, you'll have to make sure the server has been started before getting the URLs.

So, what do you do if the server application hasn't started yet? Let's look at another example.

How to get ASP.NET Core server URLs in IHostedService or BackgroundService

In some cases, you need to access the ASP.NET Core URLs outside of the Razor Pages, Controllers, or endpoints. In this case, there's no certainty the web server has been started, and no certainty the addresses collection is populated.

Luckily, there's another built-in API that can help us, the IHostApplicationLifetime. Let's take a look at an IHostedService example:

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)
    {
        this.server = server;
        this.hostApplicationLifetime = hostApplicationLifetime;
    }

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

        hostApplicationLifetime.ApplicationStarted.Register(
                () => Console.WriteLine($"Addresses after application has started: {GetAddresses()}"));

        return Task.CompletedTask;
    }

    private string GetAddresses()
    {
        var addresses = server.Features.Get<IServerAddressesFeature>().Addresses;
        return string.Join(", ", addresses);
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
Enter fullscreen mode Exit fullscreen mode

You can receive an instance of IHostApplicationLifetime through constructor dependency injection, and then use it in StartAsync to hook into its lifecycle events.

Oddly enough, the lifecycle events aren't C# events, but instead they are of type CancellationToken.

To run code once the application has started, you can pass in a lambda or delegate to hostApplicationLifetime.ApplicationStarted.Register.

The output looks like this:

Addresses before application has started:
Addresses after application has started: https://localhost:7012, http://localhost:5012
Enter fullscreen mode Exit fullscreen mode

You can also do this in the ExecuteAsync method if you use a BackgroundService:

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)
    {
        this.server = server;
        this.hostApplicationLifetime = hostApplicationLifetime;
    }

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

        await WaitForApplicationStarted();

        Console.WriteLine($"Addresses after application has started: {GetAddresses()}");
    }

    private string GetAddresses()
    {
        var addresses = server.Features.Get<IServerAddressesFeature>().Addresses;
        return string.Join(", ", addresses);
    }   

    private Task WaitForApplicationStarted()
    {
        var completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
        hostApplicationLifetime.ApplicationStarted.Register(() => completionSource.TrySetResult());
        return completionSource.Task;
    }
}
Enter fullscreen mode Exit fullscreen mode

In BackgroundService.ExecuteAsync you can properly wait by awaiting a task. Unfortunately, in IHostedService.StartAsync you cannot do this because the application builder will wait for your hosted service to start, but your hosted service would wait for the application to start, in which case neither ever finish starting.

This is probably a less common use case, but being able to access the URLs can come in really useful. For example, you can use these URLs to automatically start a ngrok tunnel and use the tunnel to respond to webhooks, which I wrote about for the Twilio blog, to be published!

I'm curious to know how you'll be using these URLs, let me know!

Top comments (0)