Quantcast
Channel: Andrew Lock | .NET Escapades
Viewing all articles
Browse latest Browse all 743

8 ways to set the URLs for an ASP.NET Core app

$
0
0

By default, without additional configuration in .NET 8, ASP.NET Core apps listen on the URL http://localhost:5000. In this post I show 8 different ways to change this URL. This is an update to a post I wrote three years ago called “5 ways to set the URLs for an ASP.NET Core app”. This post is similar but covers some additional ways! 😅

There are multiple ways to set the URLs that ASP.NET Core binds to on startup. I have a very old post about the options available in ASP.NET Core 1.0, and my previous post applies to .NET Core 3.x. The options available in ASP.NET Core 8 are similar, with a couple of additions:

We'll look at each of these options in more detail below.

What URLs can you use?

In this post I describe the "URLs" you can bind to, but you can't use just any URL. There are essentially 3 classes of URLs that you can bind:

  • The "loopback" hostname for IPv4 and IPv6 (e.g. http://localhost:5000, http://127.0.0.1:5000, or http://[::1]:5000), in the format: {scheme}://{loopbackAddress}:{port}
  • A specific IP address available on your machine (e.g. http://192.168.8.31:5005), in the format {scheme}://{IPAddress}:{port}
  • "Any" IP address for a given port (e.g. http://*:6264), in the format {scheme}://*:{port}

The port in the above patterns is also optional - if you omit it, the default port for the given scheme is used instead (port 80 for http, port 443 for https).

In .NET 8 you can also listen to requests on named pipes and Unix Sockets, in addition to TCP, which I'll discuss in a different post.

Which of these patterns you choose will depend on your deployment mechanism. For example, if you're hosting multiple applications on a "bare metal" machine, you may well need to set an explicit IPAddress. If you're hosting in a container, then you can generally use an "any" IP address.

Watch out for the "any" IP address format - you don't have to use *, you can use anything that's not an IP Address and is not the loopback address like localhost. That means you can use http://*, http://+, http://mydomain, or http://example.org. All of these behave identically for configuring Kestrel, and enable listening on any IP address. Note that you should you make sure to also configure host filtering for security reasons as described in this post.

Once you know the URLs you need to listen on, you need to tell your application about them. In this post I show 8 possible ways of doing that.

UseUrls()

The first, and easiest, option to set the binding URLs is to hardcode them when configuring the WebApplicationBuilder using UseUrls(). The UseUrls() method is an extension method exposed on the WebHost property:

var builder = WebApplication.CreateBuilder(args);
// Configure the URLs 👇
builder.WebHost.UseUrls("http://localhost:5003", "https://localhost:5004");

var app = builder.Build();

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

app.Run();

The UseUrls() method has been around since version 1.0 of ASP.NET Core, but since .NET 6 there's also a newer approach using WebApplication directly.

WebApplication.Urls and WebApplication.Run()

WebApplication exposes a Urls property for configuring which URLs the application should listen to:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Urls.Add("http://*:5003"); // 👈 Add the URL
app.Run();

Behind the scenes, the Urls property exposes the IServerAddressFeature.Addresses collection, so you're adding the addresses directly there:

public ICollection<string> Urls => ServerFeatures.GetRequiredFeature<IServerAddressesFeature>().Addresses;

A similar, related way to set the URL is to provide it in the call to app.Run(). The URL you pass here is added to the IServerAddressesFeature collection just before the application is started.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

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

app.Run("http://*:5003"); // 👈 Use the provided URL when running the application

Note that you can only provide a single URL when using the app.Run() approach, so you need to use one of the other approaches if you want to set multiple URLs.

Hard-coding the URLs never feels like a particularly clean or extensible solution, so you probably won't use this approach in general. Luckily, you can also load the URLs from configuration files, from environment variables, or from command line arguments.

Environment variables for the URL

ASP.NET Core has an advanced, extensible, configuration system that can load values from multiple configuration sources. By default, the ConfigurationManager used in .NET 8 loads values from the following locations:

  • appsettings.json JSON file
  • Optional environment-specific JSON file, appsettings.ENVIRONMENT.json
  • User Secrets
  • Environment variables
  • Command-line arguments

You can add additional providers to load from other locations, but these are the ones added by default. You can use this system to load settings and customise your application, but ASP.NET Core also uses it to configure its internals.

ASP.NET Core adds all the following to the configuration system:

  • All environment variables are added directly to configuration.
  • Environment variables that have the prefix DOTNET_ have the prefix removed and are added to the collection.
  • For ASP.NET Core apps, environment variables that have the prefix ASPNETCORE_ have the prefix removed and are added to the collection These aren't added if you are creating a generic-host-based worker service (using HostApplicationBuilder).

If you don't override them manually with UseUrls(), then ASP.NET Core will use the value of the URLS key from the configuration system. Based on the description above you can set the URLS using any of the following environment variables (casing doesn't matter):

  • URLS
  • ASPNETCORE_URLS
  • DOTNET_URLS

The order above also describes the order of precedence if you set all these environment variables, with URLS having the highest precedence,

You can set environment variables in the usual way based on your environment. For example, using the command line:

setx ASPNETCORE_URLS "http://localhost:5001"

using powershell

$Env:ASPNETCORE_URLS="http://localhost:5001"

or using bash:

export ASPNETCORE_URLS="http://localhost:5001;https://localhost:5002"

As you can see above, you can also pass multiple addresses to listen on (using HTTP or HTTPS) by separating them with a semicolon.

Environment variables for the ports

The ASPNETCORE_URLS and similar environment variables can be used to set the URLs using the formats shown at the start of this post, for example:

  • ASPNETCORE_URLS="http://localhost:5001"—listen to port 5001 on the loopback (localhost address).
  • ASPNETCORE_URLS="http://192.168.8.31:5001"—listen to port 5001 on the specified IP address.
  • ASPNETCORE_URLS="http://*:5001"—listen to port 5001 on any IP address

One thing that's always bugged me a little is the name of this variable. It implies (to me) that you can use things like http://example.com:5000 and your app will listen on that DNS name. That's not the case. The above URL is treated exactly the same as http://*:5000, i.e. it configures the app to listen to port 5000 on any IP address.

.NET 8 added new configuration keys to be more explicit about this. Instead of specifying "URLs" you specify HTTP_PORTS and HTTPS_PORTS, and these are used to bind any IP address. You can specify multiple ports using a semicolon separator. For example, if you set the following environment variables:

  • ASPNETCORE_HTTP_PORTS=5001;5002
  • ASPNETCORE_HTTPS_PORTS=5003

These are expanded to the following URLs:

  • http://*:5001
  • http://*:5002
  • https://*:5003

Just like other configuration keys, you can use ASPNETCORE_, DOTNET_, or "no prefix" to specify the values:

  • HTTP_PORTS (and HTTPS_PORTS)
  • ASPNETCORE_HTTP_PORTS (and ASPNETCORE_HTTPS_PORTS)
  • DOTNET_HTTP_PORTS (or DOTNET_HTTPS_PORTS)

Note that if you specify one of these and ASPNETCORE_URLS (for example) then the ASPNETCORE_URLS value takes precedence, and a warning is logged to the output:

warn: Microsoft.AspNetCore.Hosting.Diagnostics[15]
      Overriding HTTP_PORTS '5002' and HTTPS_PORTS ''. Binding to values defined by URLS instead 'http://*:5003'.

In .NET 8, the ASPNETCORE_HTTP_PORTS variable is set to 8080 by default in the .NET 8 docker images (whereas ASPNETCORE_URLS was set in previous version docker images). You can override this default value by setting ASPNETCORE_HTTP_PORTS or ASPNETCORE_URLS (among other ways).

Environment variables are just one way to add values to configuration; command line arguments are another.

Command line arguments

Another way to add values to ASP.NET Core's configuration system is to use the command line. Command line arguments override the value of the environment variables if they're set. Simply pass the "un-prefixed" environment variable as an argument when running the application, and prefix with --. For example:

dotnet run -- --urls "http://localhost:5100"

As before, you can pass multiple URLs to listen on by separating them with a semicolon:

dotnet run -- --urls "http://*:5100;http://localhost:5101"

This also works with the dotnet runtime version where you pass the path of the compiled dll to dotnet instead of using dotnet run:

dotnet ./WebApplication1.dll --urls "http://*:5100;http://localhost:5101"

You can also use --http_ports and --https_ports:

dotnet run -- --http_ports "5100;5101" --https_ports "5003;5004"

Note that the extra -- in the dotnet run command is not a typo; it's to ensure the arguments are definitely interpreted as arguments for configuration, not as arguments to the run command itself.

Environment variables and command line arguments are common ways to add values to ASP.NET Core configuration, but you can use any provider. One of the most frequently used ways to add values to configuration (because it's part of all the default templates) is the appsettings.json file.

appsettings.json

appsettings.json, and the environment-specific appsettings.Development.json files are included in virtually every modern .NET app template, and provide an easy way to add values to the ASP.NET Core configuration system.

Hopefully it's no surprise that you can use these files to specify the URLs your app listens on using the same urls, http_ports and https_ports keys you've seen already. The following adds the urls key to the default template:

{
  "urls": "http://*:5003", // 👈 Specify the URL
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

This version adds the http_ports and https_ports keys:

{
  "http_ports": "5001;5002", // 👈 Expands to http://*:5001 and http://*:5002
  "https_ports": "5003", // 👈 Expands to http2://*:5003
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

If you want to use different ports in development and production you can use the appsettings.json file for production, and the appsettings.Development.json for development. Or whatever pattern works for you.

Alternatively, you can rely on a different JSON file, launchSettings.json.

launchSettings.json

In addition to the appsettings.json file, most .NET project templates also include a launchSettings.json file in the Properties folder. This file doesn't add directly to your configuration; instead it contains various profiles for launching your ASP.NET Core application in development using dotnet run.

A typical file contains one or more definitions for launching the profile directly from the command line and one definition for launching the profile using IIS Express. This file drives the Debug drop-down in Visual Studio:

The launchsettings.json drives the Visual Studio Debug view

launchSettings.json provides an easy way to set the application URLs via the applicationUrl property - you can see one under the iisSettings for IIS express, and one under each of the http and https profiles:

{
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:49412", // 👈 URL to use with IIS Express profile
      "sslPort": 44381
    }
  },
  "profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "http://localhost:5005", // 👈 HTTP-only profile
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "https": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "https://localhost:7045;http://localhost:5005", // 👈 HTTP & HTTPS profile
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

You don't need to do anything special to use this file—dotnet run will pick it up automatically.

launchSettings.json also provides an easy way to set additional environment variables using the environmentVariables, as you can see from the file above.

When you run your app from the command line with dotnet run, your app will use the applicationUrl properties in the "Project" command: http://localhost:5005 in the http profile above. When you run the app using the "IISExpress" command, your app will use the applicationUrl from the iisSettings.iisExpress node: http://localhost:49412.

This file is the easiest way to configure your environment when developing locally. In fact, you have to go out of your way to not use the launchSettings.json:

dotnet run --no-launch-profile

This will skip over the launchSettings.json file and fall back to configuration to determine the URLs instead.

All of the approaches shown so far set the URLs for Kestrel indirectly, but you can also set them directly.

KestrelServerOptions.Listen()

Kestrel is configured by default in most ASP.NET Core apps. If you wish, you can configure the endpoints for Kestrel manually, or by configuring KestrelServerOptions using the IConfiguration system, instead of using the higher-level configuration provided by ASPNETCORE_URLS et al.

Kestrel is used by default when you self-host your application or when you run behind IIS out-of process. When you run in-process, an IIS HTTP Server is used. On Windows, you can also use the HTTP.sys kernel driver.

You're most likely to need to use the Kestrel Listen() functions to configure HTTPS certificates, SSL/TLS protocols and cipher suites, and Server Name Indications (SNI) configurations. There's a lot of configuration options available, so for the most part I suggest referring to the documentation. As an example, you can use the Listen() functions exposed by KestrelServerOptions like this:

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(opts =>
{
    opts.Listen(IPAddress.Loopback, port: 5002); // listen on http://localhost:5002
    opts.ListenAnyIP(5003); // listen on http://*:5003
    opts.ListenLocalhost(5004, listenOptions => listenOptions.UseHttps());  // listen on https://localhost:5004
    opts.ListenAnyIP(5005, listenOptions => // listen on https://*:5005
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
    });
});

var app = builder.Build();

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

app.Run();

This configuration sets Kestrel listening on multiple addresses. When you set the URLs for kestrel in this way, it overrides the URLS configuration value if you've set it through one of the other mechanisms, such as through environment variables. You'll see a warning in the logs if that happens:

warn: Microsoft.AspNetCore.Server.Kestrel[0]
      Overriding address(es) 'http://localhost:5165'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead.
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://127.0.0.1:5002
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://[::]:5003
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:5004
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://[::]:5005

The Kestrel configuration was hard-coded in the example above, but it doesn't have to be—you can bind it using IConfiguration instead, similar to how you can bind to urls or http_ports. For example, the above Kestrel configuration could be configured in appsettings.json instead:

{
  "Kestrel": {
    "Endpoints": {
      "HttpLoopback": {
        "Url": "http://localhost:5002"
      },
      "HttpAny": {
        "Url": "http://*:5003"
      },
      "HttpsDefaultCert": {
        "Url": "https://localhost:5004"
      },
      "HttpsInlineCertFile": {
        "Url": "https://*:5005",
        "Certificate": {
          "Path": "testCert.pfx",
          "Password": "testPassword"
        }
      }
    }
  }
}

This has the nice benefit of allowing complete configuration of your app's binding and HTTPS certificate configuration from the IConfiguration. That means you can also take advantage of secure secret storage like Key Vault for storing your certificates and certificate passwords. If you use the appsettings.json configuration above, you don't need to call ConfigureKestrel() at all.

Summary

In this post I showed eight different ways you can set the URLs that your application listens on. UseUrls() and WebApplication.Urls are the simplest, but generally aren't suitable for production workloads on their own. The --urls, --http_ports, and --https_ports command line arguments and ASPNETCORE_/DOTNET_ environment variables are most useful for setting the values in production. The launchSettings.json file is very useful for setting the URLs in a development environment. If you need fine-grained control over your configuration, you can use Kestrel's Listen* options directly. These can also be loaded from configuration for easy use in both production and development.


Viewing all articles
Browse latest Browse all 743

Trending Articles