Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add error handling for URL parameter resolution in external service
  • Loading branch information
davidfowl authored and github-actions committed Jul 12, 2025
commit 34f84ebbfbf54994bc65a3fde9ce61bbbe4749d6
21 changes: 18 additions & 3 deletions src/Aspire.Hosting/ExternalServiceBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,24 @@ private static IResourceBuilder<ExternalServiceResource> AddExternalServiceImpl(
if (uri is null)
{
// If the URI is not set, it means we are using a parameterized URL
var url = resource.UrlParameter is null
? null
: await resource.UrlParameter.GetValueAsync(ct).ConfigureAwait(false);
string? url;
try
{
url = resource.UrlParameter is null
? null
: await resource.UrlParameter.GetValueAsync(ct).ConfigureAwait(false);
}
catch (Exception ex)
{
e.Logger.LogError(ex, "Failed to get value for URL parameter '{ParameterName}'", resource.UrlParameter?.Name);

await e.Notifications.PublishUpdateAsync(resource, snapshot => snapshot with
{
State = KnownResourceStates.FailedToStart
}).ConfigureAwait(false);

return;
}

if (!ExternalServiceResource.UrlIsValidForExternalService(url, out uri, out var message))
{
Expand Down
81 changes: 81 additions & 0 deletions tests/Aspire.Hosting.Tests/ExternalServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,87 @@ public void ExternalServiceUrlValidationHelper()
Assert.Contains("absolute path must be \"/\"", pathMessage);
}

[Fact]
public async Task ExternalServiceWithParameterGetValueAsyncErrorMarksAsFailedToStart()
{
using var builder = TestDistributedApplicationBuilder.Create();

// Create a parameter with a broken value callback
var urlParam = builder.AddParameter("failing-url", () => throw new InvalidOperationException("Parameter resolution failed"));
var externalService = builder.AddExternalService("external", urlParam);

using var app = builder.Build();

// Start the app to trigger InitializeResourceEvent
var appStartTask = app.StartAsync();

// Wait for the resource to be marked as FailedToStart
var resourceEvent = await app.ResourceNotifications.WaitForResourceAsync(
externalService.Resource.Name,
e => e.Snapshot.State?.Text == KnownResourceStates.FailedToStart
).DefaultTimeout();

// Verify the resource is in the correct state
Assert.Equal(KnownResourceStates.FailedToStart, resourceEvent.Snapshot.State?.Text);

await app.StopAsync();
await appStartTask; // Ensure start completes
}

[Fact]
public async Task ExternalServiceWithParameterInvalidUrlMarksAsFailedToStart()
{
using var builder = TestDistributedApplicationBuilder.Create();

// Create a parameter that returns an invalid URL
var urlParam = builder.AddParameter("invalid-url", () => "invalid-url-not-absolute");
var externalService = builder.AddExternalService("external", urlParam);

using var app = builder.Build();

// Start the app to trigger InitializeResourceEvent
var appStartTask = app.StartAsync();

// Wait for the resource to be marked as FailedToStart
var resourceEvent = await app.ResourceNotifications.WaitForResourceAsync(
externalService.Resource.Name,
e => e.Snapshot.State?.Text == KnownResourceStates.FailedToStart
).DefaultTimeout();

// Verify the resource is in the correct state
Assert.Equal(KnownResourceStates.FailedToStart, resourceEvent.Snapshot.State?.Text);

await app.StopAsync();
await appStartTask; // Ensure start completes
}

[Fact]
public async Task ExternalServiceWithValidParameterMarksAsRunning()
{
using var builder = TestDistributedApplicationBuilder.Create();

// Create a parameter that returns a valid URL
var urlParam = builder.AddParameter("valid-url", () => "https://example.com/");
var externalService = builder.AddExternalService("external", urlParam);

using var app = builder.Build();

// Start the app to trigger InitializeResourceEvent
var appStartTask = app.StartAsync();

// Wait for the resource to be marked as Running
var resourceEvent = await app.ResourceNotifications.WaitForResourceAsync(
externalService.Resource.Name,
e => e.Snapshot.State?.Text == KnownResourceStates.Running
).DefaultTimeout();

// Verify the resource is in the correct state
Assert.Equal(KnownResourceStates.Running, resourceEvent.Snapshot.State?.Text);

await app.StopAsync();
await appStartTask; // Ensure start completes
}

private sealed class TestProject : IProjectMetadata
{
public string ProjectPath => "testproject";
Expand Down
Loading