Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service discovery / host name resolution does not work #6864

Closed
Lyra1337 opened this issue Dec 4, 2024 · 8 comments
Closed

Service discovery / host name resolution does not work #6864

Lyra1337 opened this issue Dec 4, 2024 · 8 comments

Comments

@Lyra1337
Copy link

Lyra1337 commented Dec 4, 2024

Hi,
I'm unsure if this is a problem related to aspire or my understanding of the service discovery.

I'm trying to host a MailHog instance and want to send a E-Mail to it.

AppHost/Program.cs

var builder = DistributedApplication.CreateBuilder(args);

var mailServer = builder.AddContainer("mailhog", "mailhog/mailhog")
    .WithEndpoint(
        name: "mailhog-smtp",
        port: 1025,
        targetPort: 1025
    )
    .WithHttpEndpoint(
        name: "mailhog-web",
        port: 8025,
        targetPort: 8025
    );

var apiService = builder.AddProject<Projects.AspireMailHog_ApiService>("apiservice")
    .WithReference(mailServer.GetEndpoint("mailhog-smtp"))
    .WaitFor(mailServer);

builder.AddProject<Projects.AspireMailHog_Web>("webfrontend")
    .WithExternalHttpEndpoints()
    .WithReference(apiService)
    .WaitFor(apiService);

builder.Build().Run();

ApiService/Program.cs:

var app = builder.Build();

// [...]

var mail = new MailMessage("[email protected]", "[email protected]", "Test", "Test");
var client = new SmtpClient("mailhog-smtp", 1025);
client.Send(mail);

app.Run();

The web interface is working fine and the server seems up.

I'm receiving a System.Net.Sockets.SocketException (0x00002AF9): No such host is known.

System.Net.Mail.SmtpException: Failure sending mail.
 ---> System.Net.Sockets.SocketException (0x00002AF9): No such host is known.
   at System.Net.Dns.GetHostEntryOrAddressesCore(String hostName, Boolean justAddresses, AddressFamily addressFamily, Nullable`1 activityOrDefault)
   at System.Net.Dns.GetHostAddresses(String hostNameOrAddress, AddressFamily family)
   at System.Net.Sockets.Socket.Connect(String host, Int32 port)
   at System.Net.Sockets.TcpClient.Connect(String hostname, Int32 port)
   at System.Net.Mail.SmtpConnection.GetConnection(String host, Int32 port)
   at System.Net.Mail.SmtpTransport.GetConnection(String host, Int32 port)
   at System.Net.Mail.SmtpClient.Send(MailMessage message)
   --- End of inner exception stack trace ---
   at System.Net.Mail.SmtpClient.Send(MailMessage message)
   at Program.<Main>$(String[] args) in C:\Users\thomas\source\repos\AspireMailHog\AspireMailHog.ApiService\Program.cs:line 44

Here is a sample repo, which will show the issue:

https://github.com/Lyra1337/aspire-mailhog

Thank you in advance.

@davidfowl
Copy link
Member

davidfowl commented Dec 5, 2024

Yes this won’t work. Service discovery only works via the http client factory.

You can however manually grab the endpoint via the naming convention via configuration

@Lyra1337
Copy link
Author

Lyra1337 commented Dec 5, 2024

@davidfowl Could you point me to an example, on how to use the naming conventions? I'm already naming the endpoint and using that exact name for the smtp server host name:

var mailServer = builder.AddContainer("mailhog", "mailhog/mailhog")
    .WithEndpoint(
        name: "mailhog-smtp",
        port: 1025,
        targetPort: 1025
    )

and

var client = new SmtpClient("mailhog-smtp", 1025);

There is also a method WithHttpEndpoint. If this does not work with non-http endpoints, the naming is confusing. Furthermore, the documentation of the scheme argument of the WithEndpoint methods says the following:

   scheme:
     An optional scheme e.g. (http/https). Defaults to "tcp" if not specified.

@davidfowl
Copy link
Member

The overview doc on the app host explains the format:

https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/app-host-overview?tabs=docker#connection-string-and-endpoint-references

Related: The example in our docs for writing a custom integration is an smtp server (maildev) https://learn.microsoft.com/en-us/dotnet/aspire/extensibility/custom-hosting-integration.

@Lyra1337
Copy link
Author

Lyra1337 commented Dec 5, 2024

@davidfowl Thank you for this oddly-related resource:
https://learn.microsoft.com/en-us/dotnet/aspire/extensibility/custom-hosting-integration?tabs=windows
I replicated all steps with the mailhog container, and still getting the exact same error.

I updated the repo with the same steps described in the docs:
https://github.com/Lyra1337/aspire-mailhog

@joperezr joperezr added the untriaged New issue has not been triaged label Dec 9, 2024
@DamianEdwards
Copy link
Member

DamianEdwards commented Dec 9, 2024

@davidfowl this makes we wonder whether we shouldn't have extension methods for Microsoft.Extensions.Configuration for easily getting the endpoint addresses from a service name. Although as we've discussed before it's probably better to provide better access at the service resolver layer, rather than the address provider.

@DamianEdwards
Copy link
Member

@Lyra1337 you need to get the address as a connection string, e.g.:

var mail = new MailMessage("[email protected]", "[email protected]", "Test", "Test");
var mailServerConnectionUri = new Uri(app.Configuration.GetConnectionString("mailhog")
    ?? throw new InvalidOperationException("Missing required connection string 'mailhog' or the value provided is not a valid URI."));
var client = new SmtpClient(mailServerConnectionUri.Host, mailServerConnectionUri.Port);
client.Send(mail);

I made this change on a clone of your repo and it worked:
Image

@Lyra1337
Copy link
Author

@DamianEdwards thank you for pointing me in the right direction.

After changing the code according to your suggestion, it's working properly.

To clarify things for the future and provide additional documentation for users with the same misunderstanding, I'll point out my learnings:

public sealed class MailHogResource(string name) : ContainerResource(name), IResourceWithConnectionString
{
    // Constants used to refer to well known-endpoint names, this is specific
    // for each resource type. MailHog exposes an SMTP endpoint and a HTTP
    // endpoint.
    internal const string SmtpEndpointName = "mailhog-smtp";
    internal const string HttpEndpointName = "mailhog-http";

    // An EndpointReference is a core .NET Aspire type used for keeping
    // track of endpoint details in expressions. Simple literal values cannot
    // be used because endpoints are not known until containers are launched.
    private EndpointReference? smtpReference;

    public EndpointReference SmtpEndpoint => this.smtpReference ??= new(this, SmtpEndpointName);

    // Required property on IResourceWithConnectionString. Represents a connection
    // string that applications can use to access the MailHog server. In this case
    // the connection string is composed of the SmtpEndpoint endpoint reference.
    public ReferenceExpression ConnectionStringExpression =>
        ReferenceExpression.Create(
            handler: $"smtp://{this.SmtpEndpoint.Property(EndpointProperty.Host)}:{this.SmtpEndpoint.Property(EndpointProperty.Port)}"
        );
}

The well known endpoint names are suggestions to the container names, to be used by aspire when deploying to kubernetes or docker-compose (via aspir8). In the local development, those container won't be created and instead used routed via localhost to speed up the developer debug-loop. So, in production SmtpEndpoint.Property(EndpointProperty.Host) will probably return the container name of mailhog.

Is this correct?

And then I immediately got the follow-up question:

How should I provide the correct reference for the production smtp-server?

An intuitive guess would be implementing a base-resource, like SmtpResource and inherit this for both the MailHogResource and my production mail server config (probably hosted elsewhere). But that does not feel right, as it would be hard to exchange the SmtpResource with one from NuGet.

@davidfowl
Copy link
Member

davidfowl commented Jan 15, 2025

Is this correct?

Yes, I actually started to write the document for this here dotnet/docs-aspire#2340 (comment)

How should I provide the correct reference for the production smtp-server?

What's the production server and what do you want aspire to do in this case? If you want to point an external resource (non container hosted elsewhere), then you would use AddConnectionString to model an external input at publish time.

@davidfowl davidfowl added area-meta and removed area-service-discovery untriaged New issue has not been triaged labels Jan 15, 2025
@dotnet dotnet locked and limited conversation to collaborators Jan 15, 2025
@davidfowl davidfowl converted this issue into discussion #7116 Jan 15, 2025

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests

4 participants