From 2eebd53f9d55f09f80966a6ef5a53a3d81d932a1 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 28 Mar 2026 13:21:22 -0700 Subject: [PATCH] Revert durable task scheduler Azure Functions changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Aspire.slnx | 4 - Directory.Packages.props | 3 - .../AzureFunctionsWithDts.AppHost.csproj | 22 -- .../AzureFunctionsWithDts.AppHost/Program.cs | 14 - .../Properties/launchSettings.json | 41 --- .../AzureFunctionsWithDts.Functions.csproj | 43 --- .../MyOrchestrationTrigger.cs | 29 -- .../Program.cs | 12 - .../Properties/launchSettings.json | 9 - .../AzureFunctionsWithDts.Functions/host.json | 22 -- .../DurableTaskHubNameAnnotation.cs | 18 -- .../DurableTask/DurableTaskHubResource.cs | 47 ---- .../DurableTaskResourceExtensions.cs | 254 ----------------- ...TaskSchedulerConnectionStringAnnotation.cs | 18 -- ...TaskSchedulerEmulatorContainerImageTags.cs | 16 -- .../DurableTaskSchedulerEmulatorResource.cs | 21 -- .../DurableTaskSchedulerResource.cs | 61 ----- src/Aspire.Hosting.Azure.Functions/README.md | 58 ---- .../DurableTaskResourceExtensionsTests.cs | 256 ------------------ 19 files changed, 948 deletions(-) delete mode 100644 playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/AzureFunctionsWithDts.AppHost.csproj delete mode 100644 playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/Program.cs delete mode 100644 playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/Properties/launchSettings.json delete mode 100644 playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/AzureFunctionsWithDts.Functions.csproj delete mode 100644 playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/MyOrchestrationTrigger.cs delete mode 100644 playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/Program.cs delete mode 100644 playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/Properties/launchSettings.json delete mode 100644 playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/host.json delete mode 100644 src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskHubNameAnnotation.cs delete mode 100644 src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskHubResource.cs delete mode 100644 src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskResourceExtensions.cs delete mode 100644 src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerConnectionStringAnnotation.cs delete mode 100644 src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerEmulatorContainerImageTags.cs delete mode 100644 src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerEmulatorResource.cs delete mode 100644 src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerResource.cs delete mode 100644 tests/Aspire.Hosting.Azure.Tests/DurableTaskResourceExtensionsTests.cs diff --git a/Aspire.slnx b/Aspire.slnx index 4ea6dbc4cdc..52300ace4ae 100644 --- a/Aspire.slnx +++ b/Aspire.slnx @@ -142,10 +142,6 @@ - - - - diff --git a/Directory.Packages.props b/Directory.Packages.props index 6f47a76767e..7be83c1fba4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -174,9 +174,6 @@ - - - diff --git a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/AzureFunctionsWithDts.AppHost.csproj b/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/AzureFunctionsWithDts.AppHost.csproj deleted file mode 100644 index 141e339b6f1..00000000000 --- a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/AzureFunctionsWithDts.AppHost.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - Exe - $(DefaultTargetFramework) - enable - enable - true - DC3A64A6-3991-41E2-956F-BFACC8091EC1 - - - - - - - - - - - - - diff --git a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/Program.cs b/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/Program.cs deleted file mode 100644 index 44e7d42bc99..00000000000 --- a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/Program.cs +++ /dev/null @@ -1,14 +0,0 @@ -var builder = DistributedApplication.CreateBuilder(args); - -var storage = builder.AddAzureStorage("storage").RunAsEmulator(); - -var scheduler = builder.AddDurableTaskScheduler("scheduler").RunAsEmulator(); - -var taskHub = scheduler.AddTaskHub("taskhub"); - -builder.AddAzureFunctionsProject("funcapp") - .WithHostStorage(storage) - .WithEnvironment("DURABLE_TASK_SCHEDULER_CONNECTION_STRING", scheduler) - .WithEnvironment("TASKHUB_NAME", taskHub.Resource.TaskHubName); - -builder.Build().Run(); diff --git a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/Properties/launchSettings.json b/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/Properties/launchSettings.json deleted file mode 100644 index f5f441697c0..00000000000 --- a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/Properties/launchSettings.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "https://localhost:17244;http://localhost:15054", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "DOTNET_ENVIRONMENT": "Development", - "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21003", - "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22110" - } - }, - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:15054", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "DOTNET_ENVIRONMENT": "Development", - "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19010", - "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20125" - } - }, - "generate-manifest": { - "commandName": "Project", - "launchBrowser": true, - "dotnetRunMessages": true, - "commandLineArgs": "--publisher manifest --output-path aspire-manifest.json", - "applicationUrl": "http://localhost:15888", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "DOTNET_ENVIRONMENT": "Development", - "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16175" - } - } - } -} diff --git a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/AzureFunctionsWithDts.Functions.csproj b/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/AzureFunctionsWithDts.Functions.csproj deleted file mode 100644 index ca8d05550d1..00000000000 --- a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/AzureFunctionsWithDts.Functions.csproj +++ /dev/null @@ -1,43 +0,0 @@ - - - $(DefaultTargetFramework) - v4 - Exe - enable - enable - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - Never - - - - - - - - - - - - - - - - diff --git a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/MyOrchestrationTrigger.cs b/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/MyOrchestrationTrigger.cs deleted file mode 100644 index b0c9f43129d..00000000000 --- a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/MyOrchestrationTrigger.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.Azure.Functions.Worker; -using Microsoft.DurableTask; -using Microsoft.Extensions.Logging; - -public class MyOrchestrationTrigger -{ - [Function("Chaining")] - public static async Task Run( - [OrchestrationTrigger] TaskOrchestrationContext context) - { - ILogger logger = context.CreateReplaySafeLogger(nameof(MyOrchestrationTrigger)); - logger.LogInformation("Saying hello."); - var outputs = new List(); - - // Replace name and input with values relevant for your Durable Functions Activity - outputs.Add(await context.CallActivityAsync(nameof(SayHello), "Tokyo")); - outputs.Add(await context.CallActivityAsync(nameof(SayHello), "Seattle")); - outputs.Add(await context.CallActivityAsync(nameof(SayHello), "London")); - - // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"] - return outputs; - } - - [Function(nameof(SayHello))] - public static string SayHello([ActivityTrigger] string name, FunctionContext executionContext) - { - return $"Hello {name}!"; - } -} \ No newline at end of file diff --git a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/Program.cs b/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/Program.cs deleted file mode 100644 index 2ede5b1469f..00000000000 --- a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Azure.Functions.Worker.Builder; -using Microsoft.Extensions.Hosting; - -var builder = FunctionsApplication.CreateBuilder(args); - -builder.AddServiceDefaults(); - -builder.ConfigureFunctionsWebApplication(); - -var host = builder.Build(); - -host.Run(); diff --git a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/Properties/launchSettings.json b/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/Properties/launchSettings.json deleted file mode 100644 index fa595ad7768..00000000000 --- a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/Properties/launchSettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "profiles": { - "AzureFunctionsWithDts_Functions": { - "commandName": "Project", - "commandLineArgs": "--port 7071", - "launchBrowser": false - } - } -} diff --git a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/host.json b/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/host.json deleted file mode 100644 index 455e0e8d677..00000000000 --- a/playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/host.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - }, - "enableLiveMetricsFilters": true - } - }, - "extensions": { - "durableTask": { - "hubName": "%TASKHUB_NAME%", - "storageProvider": { - "type": "azureManaged", - "connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING" - } - } - }, - "telemetryMode": "openTelemetry" -} \ No newline at end of file diff --git a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskHubNameAnnotation.cs b/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskHubNameAnnotation.cs deleted file mode 100644 index a5f760aacdd..00000000000 --- a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskHubNameAnnotation.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Aspire.Hosting.ApplicationModel; - -namespace Aspire.Hosting.Azure.DurableTask; - -/// -/// Annotation that supplies the name for an existing Durable Task hub resource. -/// -/// The name of the existing Durable Task hub. -internal sealed class DurableTaskHubNameAnnotation(object hubName) : IResourceAnnotation -{ - /// - /// Gets the name of the existing Durable Task hub. - /// - public object HubName { get; } = hubName; -} diff --git a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskHubResource.cs b/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskHubResource.cs deleted file mode 100644 index 2e57c156407..00000000000 --- a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskHubResource.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Aspire.Hosting.ApplicationModel; - -namespace Aspire.Hosting.Azure.DurableTask; - -/// -/// Represents a Durable Task hub resource. A Task Hub groups durable orchestrations and activities. -/// This resource extends the scheduler connection string with the TaskHub name so that clients can -/// connect to the correct hub. -/// -/// The logical name of the Task Hub (used as the TaskHub value). -/// The durable task scheduler resource whose connection string is the base for this hub. -public sealed class DurableTaskHubResource(string name, DurableTaskSchedulerResource scheduler) - : Resource(name), IResourceWithConnectionString, IResourceWithParent -{ - /// - /// Gets the connection string expression composed of the scheduler connection string and the TaskHub name. - /// - public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create($"{Parent.ConnectionStringExpression};TaskHub={TaskHubName}"); - - /// - /// Gets the parent durable task scheduler resource that provides the base connection string. - /// - public DurableTaskSchedulerResource Parent => scheduler; - - /// - /// Gets the name of the Task Hub. If not provided, the logical name of this resource is returned. - /// - public ReferenceExpression TaskHubName => GetTaskHubName(); - - private ReferenceExpression GetTaskHubName() - { - if (this.TryGetLastAnnotation(out var taskHubNameAnnotation)) - { - return taskHubNameAnnotation.HubName switch - { - ParameterResource parameter => ReferenceExpression.Create($"{parameter}"), - string hubName => ReferenceExpression.Create($"{hubName}"), - _ => throw new InvalidOperationException($"Unexpected Task Hub name type: {taskHubNameAnnotation.HubName.GetType().Name}") - }; - } - - return ReferenceExpression.Create($"{Name}"); - } -} diff --git a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskResourceExtensions.cs b/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskResourceExtensions.cs deleted file mode 100644 index 21b5558d8e9..00000000000 --- a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskResourceExtensions.cs +++ /dev/null @@ -1,254 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Aspire.Hosting.ApplicationModel; -using Aspire.Hosting.Azure.DurableTask; -using Microsoft.Extensions.DependencyInjection; - -namespace Aspire.Hosting; - -/// -/// Extension methods for adding and configuring Durable Task resources within a distributed application. -/// -public static class DurableTaskResourceExtensions -{ - /// - /// Adds a Durable Task scheduler resource to the distributed application. - /// - /// The distributed application builder. - /// The logical name of the scheduler resource. - /// An for the scheduler resource. - /// - /// Add a Durable Task scheduler resource: - /// - /// var builder = DistributedApplication.CreateBuilder(args); - /// var scheduler = builder.AddDurableTaskScheduler("scheduler"); - /// - /// - public static IResourceBuilder AddDurableTaskScheduler(this IDistributedApplicationBuilder builder, string name) - { - var scheduler = new DurableTaskSchedulerResource(name); - - scheduler.Annotations.Add(ManifestPublishingCallbackAnnotation.Ignore); - - return builder.AddResource(scheduler); - } - - /// - /// Configures the Durable Task scheduler to use an existing scheduler instance referenced by the provided connection string. - /// No new scheduler resource is provisioned. - /// - /// The scheduler resource builder. - /// The connection string referencing the existing Durable Task scheduler instance. - /// The same instance for fluent chaining. - /// - /// The existing resource annotation is only applied when the execution context is not in publish mode. - /// - /// - /// Use an existing scheduler instead of provisioning a new one: - /// - /// var builder = DistributedApplication.CreateBuilder(args); - /// var scheduler = builder.AddDurableTaskScheduler("scheduler") - /// .RunAsExisting("Endpoint=https://example;...;"); - /// - /// - public static IResourceBuilder RunAsExisting(this IResourceBuilder builder, string connectionString) - { - if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode) - { - builder.WithAnnotation(new DurableTaskSchedulerConnectionStringAnnotation(connectionString)); - } - - return builder; - } - - /// - /// Configures the Durable Task scheduler to use an existing scheduler instance referenced by the provided connection string. - /// No new scheduler resource is provisioned. - /// - /// The scheduler resource builder. - /// The connection string parameter referencing the existing Durable Task scheduler instance. - /// The same instance for fluent chaining. - /// - /// The existing resource annotation is only applied when the execution context is not in publish mode. - /// - /// - /// Use an existing scheduler where the connection string is supplied via a parameter: - /// - /// var builder = DistributedApplication.CreateBuilder(args); - /// var schedulerConnectionString = builder.AddParameter("schedulerConnectionString"); - /// - /// var scheduler = builder.AddDurableTaskScheduler("scheduler") - /// .RunAsExisting(schedulerConnectionString); - /// - /// - public static IResourceBuilder RunAsExisting(this IResourceBuilder builder, IResourceBuilder connectionString) - { - if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode) - { - builder.WithAnnotation(new DurableTaskSchedulerConnectionStringAnnotation(connectionString.Resource)); - } - - return builder; - } - - /// - /// Configures the Durable Task scheduler to run using the local emulator (only in non-publish modes). - /// - /// The resource builder for the scheduler. - /// Callback that exposes underlying container used for emulation to allow for customization. - /// The same instance for chaining. - /// - /// Run the scheduler locally using the emulator: - /// - /// var builder = DistributedApplication.CreateBuilder(args); - /// var scheduler = builder.AddDurableTaskScheduler("scheduler") - /// .RunAsEmulator(); - /// - /// - public static IResourceBuilder RunAsEmulator(this IResourceBuilder builder, Action>? configureContainer = null) - { - ArgumentNullException.ThrowIfNull(builder); - - if (builder.ApplicationBuilder.ExecutionContext.IsPublishMode) - { - return builder; - } - - // Mark this resource as an emulator for consistent resource identification and tooling support - builder.WithAnnotation(new EmulatorResourceAnnotation()); - - builder.WithEndpoint(name: "grpc", targetPort: 8080) - .WithHttpEndpoint(name: "http", targetPort: 8081) - .WithHttpEndpoint(name: "dashboard", targetPort: 8082) - .WithUrlForEndpoint("dashboard", c => c.DisplayText = "Scheduler Dashboard") - .WithAnnotation(new ContainerImageAnnotation - { - Registry = DurableTaskSchedulerEmulatorContainerImageTags.Registry, - Image = DurableTaskSchedulerEmulatorContainerImageTags.Image, - Tag = DurableTaskSchedulerEmulatorContainerImageTags.Tag - }); - - var emulatorResource = new DurableTaskSchedulerEmulatorResource(builder.Resource); - - var surrogateBuilder = - builder - .ApplicationBuilder - .CreateResourceBuilder(emulatorResource) - .WithEnvironment( - context => - { - ReferenceExpressionBuilder namesBuilder = new(); - - var durableTaskHubNames = - builder - .ApplicationBuilder - .Resources - .OfType() - .Where(th => th.Parent == builder.Resource) - .Select(th => th.TaskHubName) - .ToList(); - - for (int i = 0; i < durableTaskHubNames.Count; i++) - { - if (i > 0) - { - namesBuilder.AppendLiteral(", "); - } - - namesBuilder.AppendFormatted(durableTaskHubNames[i]); - } - - context.EnvironmentVariables["DTS_TASK_HUB_NAMES"] = namesBuilder.Build(); - }); - - configureContainer?.Invoke(surrogateBuilder); - - return builder; - } - - /// - /// Adds a Durable Task hub resource associated with the specified scheduler. - /// - /// The scheduler resource builder. - /// The logical name of the task hub resource. - /// An for the task hub resource. - /// - /// Add a task hub under a scheduler: - /// - /// var builder = DistributedApplication.CreateBuilder(args); - /// var scheduler = builder.AddDurableTaskScheduler("scheduler").RunAsEmulator(); - /// - /// var hub = scheduler.AddTaskHub("hub") - /// .WithTaskHubName("MyTaskHub"); - /// - /// - public static IResourceBuilder AddTaskHub(this IResourceBuilder builder, string name) - { - var hub = new DurableTaskHubResource(name, builder.Resource); - - hub.Annotations.Add(ManifestPublishingCallbackAnnotation.Ignore); - - var hubBuilder = builder.ApplicationBuilder.AddResource(hub); - - hubBuilder.OnResourceReady( - async (r, e, ct) => - { - var notifications = e.Services.GetRequiredService(); - - var url = builder.Resource.IsEmulator - ? await ReferenceExpression.Create($"{r.Parent.EmulatorDashboardEndpoint}/subscriptions/default/schedulers/default/taskhubs/{r.TaskHubName}").GetValueAsync(ct).ConfigureAwait(false) - : null; - - await notifications.PublishUpdateAsync(r, snapshot => snapshot with - { - State = KnownResourceStates.Running, - Urls = url is not null - ? [new("dashboard", url, false) { DisplayProperties = new() { DisplayName = "Task Hub Dashboard" } }] - : [] - }).ConfigureAwait(false); - }); - - return hubBuilder; - } - - /// - /// Sets the name of the Durable Task hub. - /// - /// The task hub resource builder. - /// The name of the Task Hub. - /// The same instance for fluent chaining. - /// - /// Set the task hub name: - /// - /// var builder = DistributedApplication.CreateBuilder(args); - /// var scheduler = builder.AddDurableTaskScheduler("scheduler").RunAsEmulator(); - /// var hub = scheduler.AddTaskHub("hub").WithTaskHubName("MyTaskHub"); - /// - /// - public static IResourceBuilder WithTaskHubName(this IResourceBuilder builder, string taskHubName) - { - return builder.WithAnnotation(new DurableTaskHubNameAnnotation(taskHubName)); - } - - /// - /// Sets the name of the Durable Task hub using a parameter resource. - /// - /// The task hub resource builder. - /// A parameter resource that resolves to the Task Hub name. - /// The same instance for fluent chaining. - /// - /// Set the task hub name from a parameter: - /// - /// var builder = DistributedApplication.CreateBuilder(args); - /// var taskHubName = builder.AddParameter("taskHubName"); - /// - /// var scheduler = builder.AddDurableTaskScheduler("scheduler").RunAsEmulator(); - /// var hub = scheduler.AddTaskHub("hub").WithTaskHubName(taskHubName); - /// - /// - public static IResourceBuilder WithTaskHubName(this IResourceBuilder builder, IResourceBuilder taskHubName) - { - return builder.WithAnnotation(new DurableTaskHubNameAnnotation(taskHubName.Resource)); - } -} diff --git a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerConnectionStringAnnotation.cs b/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerConnectionStringAnnotation.cs deleted file mode 100644 index c14ac070c45..00000000000 --- a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerConnectionStringAnnotation.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Aspire.Hosting.ApplicationModel; - -namespace Aspire.Hosting.Azure.DurableTask; - -/// -/// Annotation that supplies the connection string for an existing Durable Task scheduler resource. -/// -/// The connection string of the existing Durable Task scheduler. -internal sealed class DurableTaskSchedulerConnectionStringAnnotation(object connectionString) : IResourceAnnotation -{ - /// - /// Gets the connection string of the existing Durable Task scheduler. - /// - public object ConnectionString { get; } = connectionString; -} diff --git a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerEmulatorContainerImageTags.cs b/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerEmulatorContainerImageTags.cs deleted file mode 100644 index 015f8bd2784..00000000000 --- a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerEmulatorContainerImageTags.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Aspire.Hosting.Azure.DurableTask; - -internal static class DurableTaskSchedulerEmulatorContainerImageTags -{ - /// mcr.microsoft.com - public const string Registry = "mcr.microsoft.com"; - - /// dts/dts-emulator - public const string Image = "dts/dts-emulator"; - - /// latest - public const string Tag = "latest"; -} diff --git a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerEmulatorResource.cs b/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerEmulatorResource.cs deleted file mode 100644 index da207a102d9..00000000000 --- a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerEmulatorResource.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Aspire.Hosting.ApplicationModel; - -namespace Aspire.Hosting.Azure.DurableTask; - -/// -/// Represents the containerized emulator resource for a . -/// This is used to host the Durable Task scheduler logic when running locally (e.g. with an Azure Functions emulator). -/// -/// The underlying durable task scheduler resource that provides naming and annotations. -/// -/// The emulator resource delegates its annotation collection to the underlying scheduler so that configuration -/// and metadata remain consistent across both representations. -/// -public sealed class DurableTaskSchedulerEmulatorResource(DurableTaskSchedulerResource scheduler) : ContainerResource(scheduler.Name) -{ - /// - public override ResourceAnnotationCollection Annotations => scheduler.Annotations; -} diff --git a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerResource.cs b/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerResource.cs deleted file mode 100644 index 49b62424fdf..00000000000 --- a/src/Aspire.Hosting.Azure.Functions/DurableTask/DurableTaskSchedulerResource.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Aspire.Hosting.ApplicationModel; - -namespace Aspire.Hosting.Azure.DurableTask; - -/// -/// Represents a Durable Task scheduler resource used in Aspire hosting that provides endpoints -/// and a connection string for Durable Task orchestration scheduling. -/// -/// The unique resource name. -public sealed class DurableTaskSchedulerResource(string name) : Resource(name), IResourceWithEndpoints, IResourceWithConnectionString -{ - /// - /// Gets the expression that resolves to the connection string for the Durable Task scheduler. - /// - public ReferenceExpression ConnectionStringExpression => CreateConnectionString(); - - internal ReferenceExpression EmulatorDashboardEndpoint => CreateDashboardEndpoint(); - - /// - /// Gets a value indicating whether the Durable Task scheduler is running using the local - /// emulator (container) instead of a cloud-hosted service. - /// - public bool IsEmulator => this.IsContainer(); - - private ReferenceExpression CreateConnectionString() - { - if (IsEmulator) - { - var grpcEndpoint = new EndpointReference(this, "grpc"); - - return ReferenceExpression.Create($"Endpoint=http://{grpcEndpoint.Property(EndpointProperty.Host)}:{grpcEndpoint.Property(EndpointProperty.Port)};Authentication=None"); - } - - if (this.TryGetLastAnnotation(out var connectionStringAnnotation)) - { - return connectionStringAnnotation.ConnectionString switch - { - ParameterResource parameterResource => ReferenceExpression.Create($"{parameterResource}"), - string value => ReferenceExpression.Create($"{value}"), - _ => throw new InvalidOperationException($"Unexpected connection string type: {connectionStringAnnotation.ConnectionString.GetType().Name}"), - }; - } - - throw new InvalidOperationException($"Unable to resolve the Durable Task Scheduler connection string. Configure the scheduler using {nameof(DurableTaskResourceExtensions.RunAsEmulator)}() or {nameof(DurableTaskResourceExtensions.RunAsExisting)}(connectionString) before accessing {nameof(ConnectionStringExpression)}."); - } - - private ReferenceExpression CreateDashboardEndpoint() - { - if (IsEmulator) - { - var dashboardEndpoint = new EndpointReference(this, "dashboard"); - - return ReferenceExpression.Create($"http://{dashboardEndpoint.Property(EndpointProperty.Host)}:{dashboardEndpoint.Property(EndpointProperty.Port)}"); - } - - throw new NotImplementedException(); - } -} diff --git a/src/Aspire.Hosting.Azure.Functions/README.md b/src/Aspire.Hosting.Azure.Functions/README.md index bcfe22d4820..357ff30a739 100644 --- a/src/Aspire.Hosting.Azure.Functions/README.md +++ b/src/Aspire.Hosting.Azure.Functions/README.md @@ -47,64 +47,6 @@ var app = builder.Build(); app.Run(); ``` -## Durable Task Scheduler (Durable Functions) - -The Azure Functions hosting library also provides resource APIs for using the Durable Task Scheduler (DTS) with Durable Functions. - -In the _AppHost.cs_ file of `AppHost`, add a Scheduler resource, create one or more Task Hubs, and pass the connection string and hub name to your Functions project: - -```csharp -using Aspire.Hosting; -using Aspire.Hosting.Azure; -using Aspire.Hosting.Azure.Functions; - -var builder = DistributedApplication.CreateBuilder(args); - -var storage = builder.AddAzureStorage("storage").RunAsEmulator(); - -var scheduler = builder.AddDurableTaskScheduler("scheduler") - .RunAsEmulator(); - -var taskHub = scheduler.AddTaskHub("taskhub"); - -builder.AddAzureFunctionsProject("funcapp") - .WithHostStorage(storage) - .WithEnvironment("DURABLE_TASK_SCHEDULER_CONNECTION_STRING", scheduler) - .WithEnvironment("TASKHUB_NAME", taskHub.Resource.TaskHubName); - -builder.Build().Run(); -``` - -### Use the DTS emulator - -`RunAsEmulator()` starts a local container running the Durable Task Scheduler emulator. - -When a Scheduler runs as an emulator, Aspire automatically provides: - -- A "Scheduler Dashboard" URL for the scheduler resource. -- A "Task Hub Dashboard" URL for each Task Hub resource. -- A `DTS_TASK_HUB_NAMES` environment variable on the emulator container listing the Task Hub names associated with that scheduler. - -### Use an existing Scheduler - -If you already have a Scheduler instance, configure the resource using its connection string: - -```csharp -var schedulerConnectionString = builder.AddParameter( - "dts-connection-string", - "Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure"); - -var scheduler = builder.AddDurableTaskScheduler("scheduler") - .RunAsExisting(schedulerConnectionString); - -var taskHubName = builder.AddParameter("taskhub-name", "mytaskhub"); -var taskHub = scheduler.AddTaskHub("taskhub").WithTaskHubName(taskHubName); -``` -## Additional documentation - -- https://learn.microsoft.com/azure/azure-functions -- https://learn.microsoft.com/azure/azure-functions/durable/durable-task-scheduler/durable-task-scheduler - ## Feedback & contributing https://github.com/microsoft/aspire diff --git a/tests/Aspire.Hosting.Azure.Tests/DurableTaskResourceExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Tests/DurableTaskResourceExtensionsTests.cs deleted file mode 100644 index 71e23e99523..00000000000 --- a/tests/Aspire.Hosting.Azure.Tests/DurableTaskResourceExtensionsTests.cs +++ /dev/null @@ -1,256 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Aspire.Hosting.ApplicationModel; -using Aspire.Hosting.Tests.Utils; -using Aspire.Hosting.Utils; - -namespace Aspire.Hosting.Azure.Tests; - -public class DurableTaskResourceExtensionsTests -{ - [Fact] - public async Task AddDurableTaskScheduler_RunAsEmulator_ResolvedConnectionString() - { - string expectedConnectionString = "Endpoint=http://localhost:8080;Authentication=None"; - - using var builder = TestDistributedApplicationBuilder.Create(); - - var dts = builder - .AddDurableTaskScheduler("dts") - .RunAsEmulator(e => - { - e.WithEndpoint("grpc", e => e.AllocatedEndpoint = new(e, "localhost", 8080)); - e.WithEndpoint("http", e => e.AllocatedEndpoint = new(e, "localhost", 8081)); - e.WithEndpoint("dashboard", e => e.AllocatedEndpoint = new(e, "localhost", 8082)); - }); - - var connectionString = await dts.Resource.ConnectionStringExpression.GetValueAsync(default); - - Assert.Equal(expectedConnectionString, connectionString); - } - - [Fact] - public async Task AddDurableTaskScheduler_RunAsExisting_ResolvedConnectionString() - { - string expectedConnectionString = "Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure"; - - using var builder = TestDistributedApplicationBuilder.Create(); - - var dts = builder - .AddDurableTaskScheduler("dts") - .RunAsExisting(expectedConnectionString); - - var connectionString = await dts.Resource.ConnectionStringExpression.GetValueAsync(default); - - Assert.Equal(expectedConnectionString, connectionString); - } - - [Fact] - public async Task AddDurableTaskScheduler_RunAsExisting_ResolvedConnectionStringParameter() - { - string expectedConnectionString = "Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure"; - - using var builder = TestDistributedApplicationBuilder.Create(); - - var connectionStringParameter = builder.AddParameter("dts-connection-string", expectedConnectionString); - - var dts = builder - .AddDurableTaskScheduler("dts") - .RunAsExisting(connectionStringParameter); - - var connectionString = await dts.Resource.ConnectionStringExpression.GetValueAsync(default); - - Assert.Equal(expectedConnectionString, connectionString); - } - - [Theory] - [InlineData(null, "mytaskhub")] - [InlineData("myrealtaskhub", "myrealtaskhub")] - public async Task AddDurableTaskHub_RunAsExisting_ResolvedConnectionStringParameter(string? taskHubName, string expectedTaskHubName) - { - string dtsConnectionString = "Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure"; - string expectedConnectionString = $"{dtsConnectionString};TaskHub={expectedTaskHubName}"; - using var builder = TestDistributedApplicationBuilder.Create(); - - var dts = builder - .AddDurableTaskScheduler("dts") - .RunAsExisting(dtsConnectionString); - - var taskHub = dts.AddTaskHub("mytaskhub"); - - if (taskHubName is not null) - { - taskHub = taskHub.WithTaskHubName(taskHubName); - } - - var connectionString = await taskHub.Resource.ConnectionStringExpression.GetValueAsync(default); - - Assert.Equal(expectedConnectionString, connectionString); - } - - [Fact] - public void AddDurableTaskScheduler_IsExcludedFromPublishingManifest() - { - using var builder = TestDistributedApplicationBuilder.Create(); - - var dts = builder.AddDurableTaskScheduler("dts"); - - Assert.True(dts.Resource.TryGetAnnotationsOfType(out var manifestAnnotations)); - var annotation = Assert.Single(manifestAnnotations); - Assert.Equal(ManifestPublishingCallbackAnnotation.Ignore, annotation); - } - - [Fact] - public void AddDurableTaskHub_IsExcludedFromPublishingManifest() - { - using var builder = TestDistributedApplicationBuilder.Create(); - - var dts = builder.AddDurableTaskScheduler("dts").RunAsExisting("Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure"); - var taskHub = dts.AddTaskHub("hub"); - - Assert.True(taskHub.Resource.TryGetAnnotationsOfType(out var manifestAnnotations)); - var annotation = Assert.Single(manifestAnnotations); - Assert.Equal(ManifestPublishingCallbackAnnotation.Ignore, annotation); - } - - [Fact] - public void RunAsExisting_InPublishMode_DoesNotApplyConnectionStringAnnotation() - { - using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); - - var dts = builder.AddDurableTaskScheduler("dts") - .RunAsExisting("Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure"); - - Assert.False(dts.ApplicationBuilder.ExecutionContext.IsRunMode); - Assert.True(dts.ApplicationBuilder.ExecutionContext.IsPublishMode); - - var ex = Assert.Throws(() => _ = dts.Resource.ConnectionStringExpression); - Assert.Contains("Unable to resolve the Durable Task Scheduler connection string", ex.Message); - } - - [Fact] - public void RunAsEmulator_InPublishMode_IsNoOp() - { - using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish); - - var dts = builder.AddDurableTaskScheduler("dts") - .RunAsEmulator(); - - Assert.False(dts.Resource.IsEmulator); - Assert.DoesNotContain(dts.Resource.Annotations, a => a is EmulatorResourceAnnotation); - - Assert.Throws(() => _ = dts.Resource.ConnectionStringExpression); - } - - [Fact] - public void RunAsEmulator_AddsEmulatorAnnotationContainerImageAndEndpoints() - { - using var builder = TestDistributedApplicationBuilder.Create(); - - var dts = builder.AddDurableTaskScheduler("dts") - .RunAsEmulator(); - - Assert.True(dts.Resource.IsEmulator); - - var emulatorAnnotation = dts.Resource.Annotations.OfType().SingleOrDefault(); - Assert.NotNull(emulatorAnnotation); - - var containerImageAnnotation = dts.Resource.Annotations.OfType().SingleOrDefault(); - Assert.NotNull(containerImageAnnotation); - Assert.Equal("mcr.microsoft.com", containerImageAnnotation.Registry); - Assert.Equal("dts/dts-emulator", containerImageAnnotation.Image); - Assert.Equal("latest", containerImageAnnotation.Tag); - - var endpointAnnotations = dts.Resource.Annotations.OfType().ToList(); - - var grpc = endpointAnnotations.SingleOrDefault(e => e.Name == "grpc"); - Assert.NotNull(grpc); - Assert.Equal(8080, grpc.TargetPort); - - var http = endpointAnnotations.SingleOrDefault(e => e.Name == "http"); - Assert.NotNull(http); - Assert.Equal(8081, http.TargetPort); - Assert.Equal("http", http.UriScheme); - - var dashboard = endpointAnnotations.SingleOrDefault(e => e.Name == "dashboard"); - Assert.NotNull(dashboard); - Assert.Equal(8082, dashboard.TargetPort); - Assert.Equal("http", dashboard.UriScheme); - } - - [Fact] - public async Task RunAsEmulator_SetsSingleDtsTaskHubNamesEnvironmentVariable() - { - using var builder = TestDistributedApplicationBuilder.Create(); - - var dts = builder.AddDurableTaskScheduler("dts").RunAsEmulator(); - - _ = dts.AddTaskHub("hub1").WithTaskHubName("realhub1"); - - var env = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dts.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance); - - Assert.Equal("realhub1", env["DTS_TASK_HUB_NAMES"]); - } - - [Fact] - public async Task RunAsEmulator_SetsMultipleDtsTaskHubNamesEnvironmentVariable() - { - using var builder = TestDistributedApplicationBuilder.Create(); - - var dts = builder.AddDurableTaskScheduler("dts").RunAsEmulator(); - - _ = dts.AddTaskHub("hub1"); - _ = dts.AddTaskHub("hub2").WithTaskHubName("realhub2"); - - var env = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dts.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance); - - Assert.Equal("hub1, realhub2", env["DTS_TASK_HUB_NAMES"]); - } - - [Fact] - public async Task RunAsEmulator_DtsTaskHubNamesOnlyIncludesHubsForSameScheduler() - { - using var builder = TestDistributedApplicationBuilder.Create(); - - var dts1 = builder.AddDurableTaskScheduler("dts1").RunAsEmulator(); - var dts2 = builder.AddDurableTaskScheduler("dts2").RunAsEmulator(); - - _ = dts1.AddTaskHub("hub1"); - _ = dts2.AddTaskHub("hub2"); - - var env1 = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dts1.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance); - var env2 = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(dts2.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance); - - Assert.Equal("hub1", env1["DTS_TASK_HUB_NAMES"]); - Assert.Equal("hub2", env2["DTS_TASK_HUB_NAMES"]); - } - - [Fact] - public async Task WithTaskHubName_Parameter_ResolvedConnectionString() - { - using var builder = TestDistributedApplicationBuilder.Create(); - - const string dtsConnectionString = "Endpoint=https://existing-scheduler.durabletask.io;Authentication=DefaultAzure"; - var hubNameParameter = builder.AddParameter("hub-name", "parameterHub"); - - var dts = builder.AddDurableTaskScheduler("dts") - .RunAsExisting(dtsConnectionString); - - var hub = dts.AddTaskHub("ignored").WithTaskHubName(hubNameParameter); - - var connectionString = await hub.Resource.ConnectionStringExpression.GetValueAsync(default); - Assert.Equal($"{dtsConnectionString};TaskHub=parameterHub", connectionString); - } - - [Fact] - public void DurableTaskSchedulerResource_WithoutEmulatorOrExistingConnectionString_Throws() - { - using var builder = TestDistributedApplicationBuilder.Create(); - - var dts = builder.AddDurableTaskScheduler("dts"); - - var ex = Assert.Throws(() => _ = dts.Resource.ConnectionStringExpression); - Assert.Contains("Unable to resolve the Durable Task Scheduler connection string", ex.Message); - } -}