Skip to main content

.NET Aspire: The price of forgetting WithReference

Recently I lost way more time than I'd like to admit on an error that turned out to be one missing line of code. The symptom looked like a networking problem. The cause was a missing WithReference call in my Aspire AppHost.

Here's the exception my proxy threw the moment it tried to forward a request to my API:

System.Net.Http.HttpRequestException: No such host is known. (api:443)
 ---> System.Net.Sockets.SocketException (11001): No such host is known.
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
   at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.InjectNewHttp2ConnectionAsync(QueueItem queueItem)
   at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionWaiter`1.WaitForConnectionWithTelemetryAsync(HttpRequestMessage request, HttpConnectionPool pool, Boolean async, CancellationToken requestCancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.Metrics.MetricsHandler.SendAsyncWithMetrics(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.SocketsHttpHandler.<SendAsync>g__CreateHandlerAndSendAsync|115_0(HttpRequestMessage request, CancellationToken cancellationToken)
   at Yarp.ReverseProxy.Forwarder.HttpForwarder.SendAsync(HttpContext context, String destinationPrefix, HttpMessageInvoker httpClient, ForwarderRequestConfig requestConfig, HttpTransformer transformer, CancellationToken cancellationToken)

A DNS lookup failing on the literal string api. Not api.something.internal, just api. That should have been the giveaway right away, but when you're staring at a YARP stack trace at the end of the day, it isn't.

The setup

I have a YARP-based reverse proxy in front of an API, both wired up through Aspire:

var api = builder.AddProject<Projects.Demo_Api>("api")
    .WithExternalHttpEndpoints();

var proxy = builder.AddProject<Projects.Demo_Proxy>("proxy")
    .WaitFor(api)
    .WithExternalHttpEndpoints();

Looks reasonable. The proxy's YARP config points at a destination named api, Aspire starts both projects fine, the dashboard shows both as healthy. And then the first request through the proxy blows up with the exception above.

The cause: a missing WithReference.

In Aspire, WithReference(api) is what injects the API's actual endpoint URLs into the referencing project as environment variables (things like services__api__https__0). YARP (and the .NET service discovery middleware underneath it) uses those environment variables to resolve a logical name like api into a real https://localhost:xxxxx address.

Without WithReference, none of those environment variables exist. So when YARP tries to resolve api, there's nothing to resolve it to, and .NET falls back to treating api as a literal hostname. DNS obviously has no idea what api is, and you get a socket exception dressed up as an HTTP error three layers removed from the actual problem.

Remark: this is easy to miss because everything else about the wiring looks correct. WaitFor(api) is there, so startup ordering is fine. WithExternalHttpEndpoints() is there. The project reference at the C# level compiles. It's specifically the environment-variable injection that's missing, and nothing in the dashboard flags that for you.

The fix

One line:

var proxy = builder.AddProject<Projects.Demo_Proxy>("proxy")
    .WithReference(api)
    .WaitFor(api)
    .WithExternalHttpEndpoints();

Add .WithReference(api) before .WaitFor(api), restart the AppHost, and the proxy resolves api correctly. Request goes through, no more socket exception.

That's it.

Takeaway

If you see a SocketException or "host unknown" against a name that looks suspiciously like an Aspire resource name rather than a real hostname (api, db, cache, whatever you called it in AddProject or AddContainer), check WithReference before you check anything else. Service discovery in Aspire is entirely opt-in per reference. Adding a project to the AppHost does not automatically expose its endpoints to the other resources, only WithReference does.

I learnt my lesson…

More information

Popular posts from this blog

Podman– Command execution failed with exit code 125

After updating WSL on one of the developer machines, Podman failed to work. When we took a look through Podman Desktop, we noticed that Podman had stopped running and returned the following error message: Error: Command execution failed with exit code 125 Here are the steps we tried to fix the issue: We started by running podman info to get some extra details on what could be wrong: >podman info OS: windows/amd64 provider: wsl version: 5.3.1 Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM Error: unable to connect to Podman socket: failed to connect: dial tcp 127.0.0.1:2655: connectex: No connection could be made because the target machine actively refused it. That makes sense as the podman VM was not running. Let’s check the VM: >podman machine list NAME         ...

Azure DevOps/ GitHub emoji

I’m really bad at remembering emoji’s. So here is cheat sheet with all emoji’s that can be used in tools that support the github emoji markdown markup: All credits go to rcaviers who created this list.

VS Code Planning mode

After the introduction of Plan mode in Visual Studio , it now also found its way into VS Code. Planning mode, or as I like to call it 'Hannibal mode', extends GitHub Copilot's Agent Mode capabilities to handle larger, multi-step coding tasks with a structured approach. Instead of jumping straight into code generation, Planning mode creates a detailed execution plan. If you want more details, have a look at my previous post . Putting plan mode into action VS Code takes a different approach compared to Visual Studio when using plan mode. Instead of a configuration setting that you can activate but have limited control over, planning is available as a separate chat mode/agent: I like this approach better than how Visual Studio does it as you have explicit control when plan mode is activated. Instead of immediately diving into execution, the plan agent creates a plan and asks some follow up questions: You can further edit the plan by clicking on ‘Open in Editor’: ...