.NET Aspire inner-loop networking overview
One of the advantages of developing with .NET Aspire is that it enables you to develop, test, and debug cloud-native apps locally. Inner-loop networking is a key aspect of .NET Aspire that allows your apps to communicate with each other in your development environment. In this article, you learn how .NET Aspire handles various networking scenarios with proxies, service bindings, endpoint configurations, and launch profiles.
Networking in the inner loop
The inner loop is the process of developing and testing your app locally before deploying it to a target environment. .NET Aspire provides several tools and features to simplify and enhance the networking experience in the inner loop, such as:
- Launch profiles: Launch profiles are configuration files that specify how to run your app locally. You can use launch profiles (such as the launchSettings.json file) to define the service bindings, environment variables, and launch settings for your app.
- Kestrel configuration: Kestrel configuration allows you to specify the endpoints that the Kestrel web server listens on. You can configure Kestrel endpoints in your app settings, and .NET Aspire automatically uses these settings to create service bindings.
- Service bindings/Endpoint configurations: Service bindings are the connections between your app and the services it depends on, such as databases, message queues, or APIs. Service bindings provide information such as the service name, host port, scheme, and environment variable. You can add service bindings to your app either implicitly (via launch profiles) or explicitly by calling WithEndpoint.
- Proxies: .NET Aspire automatically launches a proxy for each service binding you add to your app, and assigns a port for the proxy to listen on. The proxy then forwards the requests to the port that your app listens on, which might be different from the proxy port. This way, you can avoid port conflicts and access your app and services using consistent and predictable URLs.
How service bindings work
A service binding in .NET Aspire involves two integrations: a service representing an external resource your app requires (for example, a database, message queue, or API), and a binding that establishes a connection between your app and the service and provides necessary information.
.NET Aspire supports two service binding types: implicit, automatically created based on specified launch profiles defining app behavior in different environments, and explicit, manually created using WithEndpoint.
Upon creating a binding, whether implicit or explicit, .NET Aspire launches a lightweight reverse proxy on a specified port, handling routing and load balancing for requests from your app to the service. The proxy is a .NET Aspire implementation detail, requiring no configuration or management concern.
To help visualize how service bindings work, consider the .NET Aspire starter templates inner-loop networking diagram:
Launch profiles
When you call AddProject, the app host looks for Properties/launchSettings.json to determine the default set of service bindings. The app host selects a specific launch profile using the following rules:
- An explicit
launchProfileName
argument passed when callingAddProject
. - The
DOTNET_LAUNCH_PROFILE
environment variable. For more information, see .NET environment variables. - The first launch profile defined in launchSettings.json.
Consider the following launchSettings.json file:
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7239;http://localhost:5066",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
For the remainder of this article, imagine that you've created an IDistributedApplicationBuilder assigned to a variable named builder
with the CreateBuilder() API:
var builder = DistributedApplication.CreateBuilder(args);
To specify the http and https launch profiles, configure the applicationUrl
values for both in the launchSettings.json file. These URLs are used to create service bindings for this project. This is the equivalent of:
builder.AddProject<Projects.Networking_Frontend>("frontend")
.WithHttpEndpoint(port: 5066)
.WithHttpsEndpoint(port: 7239);
Important
If there's no launchSettings.json (or launch profile), there are no bindings by default.
For more information, see .NET Aspire and launch profiles.
Kestrel configured endpoints
.NET Aspire supports Kestrel endpoint configuration. For example, consider an appsettings.json file for a project that defines a Kestrel endpoint with the HTTPS scheme and port 5271:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://*:5271"
}
}
}
}
The preceding configuration specifies an Https
endpoint. The Url
property is set to https://*:5271
, which means the endpoint listens on all interfaces on port 5271. For more information, see Configure endpoints for the ASP.NET Core Kestrel web server.
With the Kestrel endpoint configured, the project should remove any configured applicationUrl
from the launchSettings.json file.
Note
If the applicationUrl
is present in the launchSettings.json file and the Kestrel endpoint is configured, the app host will throw an exception.
When you add a project resource, there's an overload that lets you specify that the Kestrel endpoint should be used instead of the launchSettings.json file:
builder.AddProject<Projects.Networking_ApiService>(
name: "apiservice",
configure: static project =>
{
project.ExcludeLaunchProfile = true;
project.ExcludeKestrelEndpoints = false;
})
.WithHttpsEndpoint();
For more information, see AddProject.
Ports and proxies
When defining a service binding, the host port is always given to the proxy that sits in front of the service. This allows single or multiple replicas of a service to behave similarly. Additionally, all resource dependencies that use the WithReference API rely of the proxy endpoint from the environment variable.
Consider the following method chain that calls AddProject, WithHttpEndpoint, and then WithReplicas:
builder.AddProject<Projects.Networking_Frontend>("frontend")
.WithHttpEndpoint(port: 5066)
.WithReplicas(2);
The preceding code results in the following networking diagram:
The preceding diagram depicts the following:
- A web browser as an entry point to the app.
- A host port of 5066.
- The frontend proxy sitting between the web browser and the frontend service replicas, listening on port 5066.
- The
frontend_0
frontend service replica listening on the randomly assigned port 65001. - The
frontend_1
frontend service replica listening on the randomly assigned port 65002.
Without the call to WithReplicas
, there's only one frontend service. The proxy still listens on port 5066, but the frontend service listens on a random port:
builder.AddProject<Projects.Networking_Frontend>("frontend")
.WithHttpEndpoint(port: 5066);
There are two ports defined:
- A host port of 5066.
- A random proxy port that the underlying service will be bound to.
The preceding diagram depicts the following:
- A web browser as an entry point to the app.
- A host port of 5066.
- The frontend proxy sitting between the web browser and the frontend service, listening on port 5066.
- The frontend service listening on random port of 65001.
The underlying service is fed this port via ASPNETCORE_URLS
for project resources. Other resources access to this port by specifying an environment variable on the service binding:
builder.AddNpmApp("frontend", "../NodeFrontend", "watch")
.WithHttpEndpoint(port: 5067, env: "PORT");
The previous code makes the random port available in the PORT
environment variable. The app uses this port to listen to incoming connections from the proxy. Consider the following diagram:
The preceding diagram depicts the following:
- A web browser as an entry point to the app.
- A host port of 5067.
- The frontend proxy sitting between the web browser and the frontend service, listening on port 5067.
- The frontend service listening on an environment 65001.
Tip
To avoid an endpoint being proxied, set the IsProxied
property to false
when calling the WithEndpoint
extension method. For more information, see Endpoint extensions: additional considerations.
Omit the host port
When you omit the host port, .NET Aspire generates a random port for both host and service port. This is useful when you want to avoid port conflicts and don't care about the host or service port. Consider the following code:
builder.AddProject<Projects.Networking_Frontend>("frontend")
.WithHttpEndpoint();
In this scenario, both the host and service ports are random, as shown in the following diagram:
The preceding diagram depicts the following:
- A web browser as an entry point to the app.
- A random host port of 65000.
- The frontend proxy sitting between the web browser and the frontend service, listening on port 65000.
- The frontend service listening on a random port of 65001.
Container ports
When you add a container resource, .NET Aspire automatically assigns a random port to the container. To specify a container port, configure the container resource with the desired port:
builder.AddContainer("frontend", "mcr.microsoft.com/dotnet/samples", "aspnetapp")
.WithHttpEndpoint(port: 8000, targetPort: 8080);
The preceding code:
- Creates a container resource named
frontend
, from themcr.microsoft.com/dotnet/samples:aspnetapp
image. - Exposes an
http
endpoint by binding the host to port 8000 and mapping it to the container's port 8080.
Consider the following diagram:
Endpoint extension methods
Any resource that implements the IResourceWithEndpoints interface can use the WithEndpoint
extension methods. There are several overloads of this extension, allowing you to specify the scheme, container port, host port, environment variable name, and whether the endpoint is proxied.
There's also an overload that allows you to specify a delegate to configure the endpoint. This is useful when you need to configure the endpoint based on the environment or other factors. Consider the following code:
builder.AddProject<Projects.Networking_ApiService>("apiService")
.WithEndpoint(
endpointName: "admin",
callback: static endpoint =>
{
endpoint.Port = 17003;
endpoint.UriScheme = "http";
endpoint.Transport = "http";
});
The preceding code provides a callback delegate to configure the endpoint. The endpoint is named admin
and configured to use the http
scheme and transport, as well as the 17003 host port. The consumer references this endpoint by name, consider the following AddHttpClient
call:
builder.Services.AddHttpClient<WeatherApiClient>(
client => client.BaseAddress = new Uri("http://_admin.apiservice"));
The Uri
is constructed using the admin
endpoint name prefixed with the _
sentinel. This is a convention to indicate that the admin
segment is the endpoint name belonging to the apiservice
service. For more information, see .NET Aspire service discovery.
Additional considerations
When calling the WithEndpoint
extension method, the callback
overload exposes the raw EndpointAnnotation, which allows the consumer to customize many aspects of the endpoint.
The AllocatedEndpoint
property allows you to get or set the endpoint for a service. The IsExternal
and IsProxied
properties determine how the endpoint is managed and exposed: IsExternal
decides if it should be publicly accessible, while IsProxied
ensures DCP manages it, allowing for internal port differences and replication.
Tip
If you're hosting an external executable that runs its own proxy and encounters port binding issues due to DCP already binding the port, try setting the IsProxied
property to false
. This prevents DCP from managing the proxy, allowing your executable to bind the port successfully.
The Name
property identifies the service, whereas the Port
and TargetPort
properties specify the desired and listening ports, respectively.
For network communication, the Protocol
property supports TCP and UDP, with potential for more in the future, and the Transport
property indicates the transport protocol (HTTP, HTTP2, HTTP3). Lastly, if the service is URI-addressable, the UriScheme
property provides the URI scheme for constructing the service URI.
For more information, see the available properties of the EndpointAnnotation properties.
Endpoint filtering
All .NET Aspire project resource endpoints follow a set of default heuristics. Some endpoints are included in ASPNETCORE_URLS
at runtime, some are published as HTTP/HTTPS_PORTS
, and some configurations are resolved from Kestrel configuration. Regardless of the default behavior, you can filter the endpoints that are included in environment variables by using the WithEndpointsInEnvironment extension method:
builder.AddProject<Projects.Networking_ApiService>("apiservice")
.WithHttpsEndpoint() // Adds a default "https" endpoint
.WithHttpsEndpoint(port: 19227, name: "admin")
.WithEndpointsInEnvironment(
filter: static endpoint =>
{
return endpoint.Name is not "admin";
});
The preceding code adds a default HTTPS endpoint, as well as an admin
endpoint on port 19227. However, the admin
endpoint is excluded from the environment variables. This is useful when you want to expose an endpoint for internal use only.