| File: DurableTask\DurableTaskResourceExtensions.cs | Web Access |
| Project: src\src\Aspire.Hosting.Azure.Functions\Aspire.Hosting.Azure.Functions.csproj (Aspire.Hosting.Azure.Functions) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Azure.DurableTask; using Microsoft.Extensions.DependencyInjection; namespace Aspire.Hosting; /// <summary> /// Extension methods for adding and configuring Durable Task resources within a distributed application. /// </summary> public static class DurableTaskResourceExtensions { /// <summary> /// Adds a Durable Task scheduler resource to the distributed application. /// </summary> /// <param name="builder">The distributed application builder.</param> /// <param name="name">The logical name of the scheduler resource.</param> /// <returns>An <see cref="IResourceBuilder{TResource}"/> for the scheduler resource.</returns> /// <ats-returns>The resource builder.</ats-returns> /// <example> /// Add a Durable Task scheduler resource: /// <code> /// var builder = DistributedApplication.CreateBuilder(args); /// var scheduler = builder.AddDurableTaskScheduler("scheduler"); /// </code> /// </example> [AspireExport] [Experimental("ASPIREDURABLETASK001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] public static IResourceBuilder<DurableTaskSchedulerResource> AddDurableTaskScheduler(this IDistributedApplicationBuilder builder, [ResourceName] string name) { var scheduler = new DurableTaskSchedulerResource(name); scheduler.Annotations.Add(ManifestPublishingCallbackAnnotation.Ignore); return builder.AddResource(scheduler) .WithIconName("CalendarClock"); } /// <summary> /// Configures the Durable Task scheduler to use an existing scheduler instance referenced by the provided connection string. /// No new scheduler resource is provisioned. /// </summary> /// <param name="builder">The scheduler resource builder.</param> /// <param name="connectionString">The connection string referencing the existing Durable Task scheduler instance.</param> /// <returns>The same <see cref="IResourceBuilder{DurableTaskSchedulerResource}"/> instance for fluent chaining.</returns> /// <remarks> /// The existing resource annotation is only applied when the execution context is not in publish mode. /// </remarks> /// <example> /// Use an existing scheduler instead of provisioning a new one: /// <code> /// var builder = DistributedApplication.CreateBuilder(args); /// var scheduler = builder.AddDurableTaskScheduler("scheduler") /// .RunAsExisting("Endpoint=https://example;...;"); /// </code> /// </example> [AspireExportIgnore(Reason = "Polyglot export is via RunAsExistingCore which accepts both string and parameter resource inputs.")] [Experimental("ASPIREDURABLETASK001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] public static IResourceBuilder<DurableTaskSchedulerResource> RunAsExisting(this IResourceBuilder<DurableTaskSchedulerResource> builder, string connectionString) { if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode) { builder.WithAnnotation(new DurableTaskSchedulerConnectionStringAnnotation(connectionString)); } return builder; } /// <summary> /// Configures the Durable Task scheduler to use an existing scheduler instance referenced by the provided connection string. /// No new scheduler resource is provisioned. /// </summary> /// <param name="builder">The scheduler resource builder.</param> /// <param name="connectionString">The connection string parameter referencing the existing Durable Task scheduler instance.</param> /// <returns>The same <see cref="IResourceBuilder{DurableTaskSchedulerResource}"/> instance for fluent chaining.</returns> /// <remarks> /// The existing resource annotation is only applied when the execution context is not in publish mode. /// </remarks> /// <example> /// Use an existing scheduler where the connection string is supplied via a parameter: /// <code> /// var builder = DistributedApplication.CreateBuilder(args); /// var schedulerConnectionString = builder.AddParameter("schedulerConnectionString"); /// /// var scheduler = builder.AddDurableTaskScheduler("scheduler") /// .RunAsExisting(schedulerConnectionString); /// </code> /// </example> [AspireExportIgnore(Reason = "Polyglot export is via RunAsExistingCore which accepts both string and parameter resource inputs.")] [Experimental("ASPIREDURABLETASK001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] public static IResourceBuilder<DurableTaskSchedulerResource> RunAsExisting(this IResourceBuilder<DurableTaskSchedulerResource> builder, IResourceBuilder<ParameterResource> connectionString) { if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode) { builder.WithAnnotation(new DurableTaskSchedulerConnectionStringAnnotation(connectionString.Resource)); } return builder; } /// <summary> /// Configures the Durable Task scheduler to use an existing scheduler instance from a connection string or parameter resource. /// </summary> [AspireExport("runAsExisting")] #pragma warning disable ASPIREDURABLETASK001 internal static IResourceBuilder<DurableTaskSchedulerResource> RunAsExistingCore( this IResourceBuilder<DurableTaskSchedulerResource> builder, [AspireUnion(typeof(string), typeof(IResourceBuilder<ParameterResource>))] object connectionString) => connectionString switch { string value => builder.RunAsExisting(value), IResourceBuilder<ParameterResource> parameter => builder.RunAsExisting(parameter), _ => throw new ArgumentException($"Unexpected connection string type: {connectionString.GetType().Name}", nameof(connectionString)) }; #pragma warning restore ASPIREDURABLETASK001 /// <summary> /// Configures the Durable Task scheduler to run using the local emulator (only in non-publish modes). /// </summary> /// <param name="builder">The resource builder for the scheduler.</param> /// <param name="configureContainer">Callback that exposes underlying container used for emulation to allow for customization.</param> /// <returns>The same <see cref="IResourceBuilder{DurableTaskSchedulerResource}"/> instance for chaining.</returns> /// <ats-returns>The resource builder.</ats-returns> /// <example> /// Run the scheduler locally using the emulator: /// <code> /// var builder = DistributedApplication.CreateBuilder(args); /// var scheduler = builder.AddDurableTaskScheduler("scheduler") /// .RunAsEmulator(); /// </code> /// </example> [AspireExport(RunSyncOnBackgroundThread = true)] [Experimental("ASPIREDURABLETASK001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] public static IResourceBuilder<DurableTaskSchedulerResource> RunAsEmulator(this IResourceBuilder<DurableTaskSchedulerResource> builder, Action<IResourceBuilder<DurableTaskSchedulerEmulatorResource>>? 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.WithHttpEndpoint(name: "grpc", targetPort: 8080) .WithEndpoint("grpc", endpoint => endpoint.Transport = "http2") .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<DurableTaskHubResource>() .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; } /// <summary> /// Adds a Durable Task hub resource associated with the specified scheduler. /// </summary> /// <param name="builder">The scheduler resource builder.</param> /// <param name="name">The logical name of the task hub resource.</param> /// <returns>An <see cref="IResourceBuilder{TResource}"/> for the task hub resource.</returns> /// <ats-returns>The resource builder.</ats-returns> /// <example> /// Add a task hub under a scheduler: /// <code> /// var builder = DistributedApplication.CreateBuilder(args); /// var scheduler = builder.AddDurableTaskScheduler("scheduler").RunAsEmulator(); /// /// var hub = scheduler.AddTaskHub("hub") /// .WithTaskHubName("MyTaskHub"); /// </code> /// </example> [AspireExport] [Experimental("ASPIREDURABLETASK001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] public static IResourceBuilder<DurableTaskHubResource> AddTaskHub(this IResourceBuilder<DurableTaskSchedulerResource> builder, [ResourceName] string name) { var hub = new DurableTaskHubResource(name, builder.Resource); hub.Annotations.Add(ManifestPublishingCallbackAnnotation.Ignore); var hubBuilder = builder.ApplicationBuilder.AddResource(hub) .WithIconName("CalendarClock"); hubBuilder.OnResourceReady( async (r, e, ct) => { var notifications = e.Services.GetRequiredService<ResourceNotificationService>(); 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; } /// <summary> /// Sets the name of the Durable Task hub. /// </summary> /// <param name="builder">The task hub resource builder.</param> /// <param name="taskHubName">The name of the Task Hub.</param> /// <returns>The same <see cref="IResourceBuilder{DurableTaskHubResource}"/> instance for fluent chaining.</returns> /// <example> /// Set the task hub name: /// <code> /// var builder = DistributedApplication.CreateBuilder(args); /// var scheduler = builder.AddDurableTaskScheduler("scheduler").RunAsEmulator(); /// var hub = scheduler.AddTaskHub("hub").WithTaskHubName("MyTaskHub"); /// </code> /// </example> [AspireExportIgnore(Reason = "Polyglot export is via WithTaskHubNameCore which accepts both string and parameter resource inputs.")] [Experimental("ASPIREDURABLETASK001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] public static IResourceBuilder<DurableTaskHubResource> WithTaskHubName(this IResourceBuilder<DurableTaskHubResource> builder, string taskHubName) { return builder.WithAnnotation(new DurableTaskHubNameAnnotation(taskHubName)); } /// <summary> /// Sets the name of the Durable Task hub using a parameter resource. /// </summary> /// <param name="builder">The task hub resource builder.</param> /// <param name="taskHubName">A parameter resource that resolves to the Task Hub name.</param> /// <returns>The same <see cref="IResourceBuilder{DurableTaskHubResource}"/> instance for fluent chaining.</returns> /// <example> /// Set the task hub name from a parameter: /// <code> /// var builder = DistributedApplication.CreateBuilder(args); /// var taskHubName = builder.AddParameter("taskHubName"); /// /// var scheduler = builder.AddDurableTaskScheduler("scheduler").RunAsEmulator(); /// var hub = scheduler.AddTaskHub("hub").WithTaskHubName(taskHubName); /// </code> /// </example> [AspireExportIgnore(Reason = "Polyglot export is via WithTaskHubNameCore which accepts both string and parameter resource inputs.")] [Experimental("ASPIREDURABLETASK001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] public static IResourceBuilder<DurableTaskHubResource> WithTaskHubName(this IResourceBuilder<DurableTaskHubResource> builder, IResourceBuilder<ParameterResource> taskHubName) { return builder.WithAnnotation(new DurableTaskHubNameAnnotation(taskHubName.Resource)); } /// <summary> /// Sets the Durable Task hub name from a string or parameter resource. /// </summary> [AspireExport("withTaskHubName")] #pragma warning disable ASPIREDURABLETASK001 internal static IResourceBuilder<DurableTaskHubResource> WithTaskHubNameCore( this IResourceBuilder<DurableTaskHubResource> builder, [AspireUnion(typeof(string), typeof(IResourceBuilder<ParameterResource>))] object taskHubName) => taskHubName switch { string value => builder.WithTaskHubName(value), IResourceBuilder<ParameterResource> parameter => builder.WithTaskHubName(parameter), _ => throw new ArgumentException($"Unexpected task hub name type: {taskHubName.GetType().Name}", nameof(taskHubName)) }; #pragma warning restore ASPIREDURABLETASK001 }