How to get ASP.NET Core's local server URLs
Niels Swimberghe - - .NET
Follow me on Twitter, buy me a coffee
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.
Check out this blog post if you want to get the full public URL of your ASP.NET Core application, or this blog post if you want to generate absolute URLs for 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 thisdotnet 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();
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
:
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:
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
:
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!
I'm curious to know how you'll be using these URLs, let me know!