Miklós Pathy
Miklós Pathy

Reputation: 1

Service Discovery for custom container in .NET Aspire

I am tring to connect to a custom container with a client in an Aspire project. The problem is, the service discovery does not works. My expectation is, that I receive a fully configured httpClient in the LabseClient.LabseTest when the LabseClient is instantieted by the ServiceProvider, but it does not happens. The configuration looks like some default with port 80, so it can not find the service in the container. It should be configured on the fly.

I created a test solution to explicitly demonstrate this specific problem. To reproduce the problem just run the AppHost, and then click one of the endpoints of the apiservice. (downloading the container takes some time, i do not wanted to change it to introduce possible addtional problems)
Here you can find the solution:

https://github.com/MiklosPathy/Aspire.ServiceDiscovery.Problem

For explanation, I copy here the relevant code parts:

Ainizeml.Labse.Hosting project was created for encapsulating the container, and provide a IResourceWithConnectionString implementation. It is necessary for the WithReference call. (or at least currently i think it is necessary)

I followed this example to creating it:
https://learn.microsoft.com/en-us/dotnet/aspire/extensibility/custom-hosting-integration?tabs=windows

    public sealed class LabseResource(string name) : ContainerResource(name), IResourceWithConnectionString
    {
        internal const string HttpEndpointName = "http";
        internal const int LabseRestAPIPort = 8501;

        private EndpointReference? _httpReference;

        public EndpointReference HttpEndpoint =>
            _httpReference ??= new(this, HttpEndpointName);

        public ReferenceExpression ConnectionStringExpression =>
            ReferenceExpression.Create(
                $"http://{HttpEndpoint.Property(EndpointProperty.Host)}:{HttpEndpoint.Property(EndpointProperty.Port)}"
            );
    }
    public static class LabseResourceBuilderExtensions
    {
        public static IResourceBuilder<LabseResource> AddLabse(
            this IDistributedApplicationBuilder builder,
            string name,
            int? httpPort = null)
        {
            var resource = new LabseResource(name);

            return builder.AddResource(resource)
                          .WithImage(LabseContainerImageTags.Image)
                          .WithImageRegistry(LabseContainerImageTags.Registry)
                          .WithImageTag(LabseContainerImageTags.Tag)
                          .WithHttpEndpoint(
                              targetPort: LabseResource.LabseRestAPIPort,
                              port: httpPort,
                              name: LabseResource.HttpEndpointName);
        }
    }
    internal static class LabseContainerImageTags
    {
        internal const string Registry = "docker.io";
        internal const string Image = "ainizeml/labse";
        internal const string Tag = "latest";
    }

The Aspire.ServiceDiscovery.AppHost Program.cs contains only the definitions for the container and the project, linked together.

using Ainizeml.Labse.Hosting;

var builder = DistributedApplication.CreateBuilder(args);

var labseModel = builder.AddLabse("labse");
var apiService = builder.AddProject<Projects.Aspire_ServiceDiscovery_WebAPI>("apiservice")
    .WithReference(labseModel)
    .WaitFor(labseModel);

builder.Build().Run();

The Aspire.ServiceDiscovery.ServiceDefaults project is auto generated by the Aspire VS project template.

In the Aspire.ServiceDiscovery.WebAPI project the LabseTest class was created as the service class for the request. The problem with the HttpClient configuration (or lack of it) appears here.

internal class LabseClient(HttpClient httpClient)
{
    public async Task<string> LabseTest()
    {
        try
        {
            var response = await httpClient.GetAsync("/v1/models/ainize/metadata");
            return response.ToString();
        }
        catch (Exception ex)
        {
            return ex.Message;
        }
    }
}

The service app builder configuration in the WebAPI Program.cs looks like this

        var builder = WebApplication.CreateBuilder(args);

        builder.AddServiceDefaults();
        builder.Services.AddProblemDetails();
        builder.Services.AddOpenApi();

        builder.Services.AddHttpClient<LabseClient>(
            static (client) =>
            {
                client.BaseAddress = new Uri("http://labse");
            }
            );

        var app = builder.Build();

        app.UseExceptionHandler();

        // Configure the HTTP request pipeline.
        if (app.Environment.IsDevelopment())
        {
            app.MapOpenApi();
        }

        app.MapGet("/", (LabseClient labseClient) => labseClient.LabseTest());

        app.MapDefaultEndpoints();

        app.Run();

What I am doing wrong?

Upvotes: 0

Views: 108

Answers (1)

Mikl&#243;s Pathy
Mikl&#243;s Pathy

Reputation: 1

I got an answer for this in the Aspire Github repo. If no connection string is necessary, simpler if the IResourceWithServiceDiscovery is implemented instead of the IResourceWithConnectionString. (LabseResource class in my case) IResourceWithServiceDiscovery also has an implementation of the WithReference extension method.

Unfortunatelly no mention about IResourceWithServiceDiscovery in the hosting integration example in the docs, so it can be a bit misleading.

Upvotes: 0

Related Questions