How to get ASP.NET Core server URLs
Tags: dotnet, aspnet, csharp
Published: 2022-01-25 22:49:24

---
title: How to get ASP.NET Core server URLs
published: true
date: 2022-01-25 00:00:00 UTC
tags: dotnet, aspnet, csharp
canonical_url: https://swimburger.net/blog/dotnet/how-to-get-aspdotnet-core-server-urls
cover_image: https://swimburger.net/media/llcack2s/social.png?anchor=center&mode=crop&width=1000&height=420
---

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](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.hostingabstractionswebhostbuilderextensions.useurls?view=aspnetcore-6.0).
- 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:

```csharp
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();
```

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

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`:

```csharp
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();
    }
}
```

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

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:

```csharp
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;
}
```

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

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

```csharp
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;
    }
}
```

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!**