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…