From fc053ecc196384c58dcefd7419cfbd57bbcf80be Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Tue, 7 Jan 2025 15:55:02 -0800
Subject: [PATCH 01/58] durable task scheduler auth extension
save
initial
---
Microsoft.DurableTask.sln | 10 +
src/Extensions/Azure/Azure.csproj | 27 +++
.../DurableTaskSchedulerConnectionString.cs | 82 ++++++++
.../Azure/DurableTaskSchedulerExtensions.cs | 156 +++++++++++++++
.../Azure/DurableTaskSchedulerOptions.cs | 147 +++++++++++++++
...rableTaskSchedulerConnectionStringTests.cs | 122 ++++++++++++
.../DurableTaskSchedulerExtensionsTests.cs | 178 ++++++++++++++++++
.../DurableTaskSchedulerOptionsTests.cs | 119 ++++++++++++
.../Extensions.Azure.Tests.csproj | 34 ++++
9 files changed, 875 insertions(+)
create mode 100644 src/Extensions/Azure/Azure.csproj
create mode 100644 src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
create mode 100644 src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
create mode 100644 src/Extensions/Azure/DurableTaskSchedulerOptions.cs
create mode 100644 test/Extensions.Azure.Tests/DurableTaskSchedulerConnectionStringTests.cs
create mode 100644 test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs
create mode 100644 test/Extensions.Azure.Tests/DurableTaskSchedulerOptionsTests.cs
create mode 100644 test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj
diff --git a/Microsoft.DurableTask.sln b/Microsoft.DurableTask.sln
index 2b8bab8a..96d6355f 100644
--- a/Microsoft.DurableTask.sln
+++ b/Microsoft.DurableTask.sln
@@ -71,6 +71,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Analyzers.Tests", "test\Ana
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsApp.Tests", "samples\AzureFunctionsUnitTests\AzureFunctionsApp.Tests.csproj", "{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{5227C712-2355-403F-90D6-51D0BCAE4D38}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure", "src\Extensions\Azure\Azure.csproj", "{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -185,6 +189,10 @@ Global
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -220,6 +228,8 @@ Global
{998E9D97-BD36-4A9D-81FC-5DAC1CE40083} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
{541FCCCE-1059-4691-B027-F761CD80DE92} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
+ {5227C712-2355-403F-90D6-51D0BCAE4D38} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
+ {662BF73D-A4DD-4910-8625-7C12F1ACDBEC} = {5227C712-2355-403F-90D6-51D0BCAE4D38}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
diff --git a/src/Extensions/Azure/Azure.csproj b/src/Extensions/Azure/Azure.csproj
new file mode 100644
index 00000000..8f1d2e35
--- /dev/null
+++ b/src/Extensions/Azure/Azure.csproj
@@ -0,0 +1,27 @@
+
+
+
+ netstandard2.0;net6.0
+ Azure extensions for the Durable Task Framework.
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs b/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
new file mode 100644
index 00000000..ae71cf21
--- /dev/null
+++ b/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
@@ -0,0 +1,82 @@
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.All rights reserved.
+// ------------------------------------------------------------
+
+using System.Data.Common;
+
+namespace DurableTask.Extensions.Azure;
+
+///
+/// Represents the constituent parts of a connection string for a Durable Task Scheduler service.
+///
+public sealed class DurableTaskSchedulerConnectionString
+{
+ readonly DbConnectionStringBuilder builder;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// A connection string for a Durable Task Scheduler service.
+ public DurableTaskSchedulerConnectionString(string connectionString)
+ {
+ this.builder = new() { ConnectionString = connectionString };
+ }
+
+ ///
+ /// Gets the authentication method specified in the connection string (if any).
+ ///
+ public string Authentication => this.GetRequiredValue("Authentication");
+
+ ///
+ /// Gets the managed identity or workload identity client ID specified in the connection string (if any).
+ ///
+ public string? ClientId => this.GetValue("ClientID");
+
+ ///
+ /// Gets the "AdditionallyAllowedTenants" property, optionally used by Workload Identity.
+ /// Multiple values can be separated by a comma.
+ ///
+ public IList? AdditionallyAllowedTenants =>
+ string.IsNullOrEmpty(this.AdditionallyAllowedTenantsStr)
+ ? null
+ : this.AdditionallyAllowedTenantsStr!.Split(',');
+
+ ///
+ /// Gets the "TenantId" property, optionally used by Workload Identity.
+ ///
+ public string? TenantId => this.GetValue("TenantId");
+
+ ///
+ /// Gets the "TokenFilePath" property, optionally used by Workload Identity.
+ ///
+ public string? TokenFilePath => this.GetValue("TokenFilePath");
+
+ ///
+ /// Gets the endpoint specified in the connection string (if any).
+ ///
+ public string Endpoint => this.GetRequiredValue("Endpoint");
+
+ ///
+ /// Gets the task hub name specified in the connection string.
+ ///
+ public string TaskHubName => this.GetRequiredValue("TaskHub");
+
+ string? AdditionallyAllowedTenantsStr => this.GetValue("AdditionallyAllowedTenants");
+
+ string? GetValue(string name) =>
+ this.builder.TryGetValue(name, out object? value)
+ ? value as string
+ : null;
+
+ string GetRequiredValue(string name)
+ {
+ string? value = this.GetValue(name);
+ if (string.IsNullOrEmpty(value))
+ {
+ throw new ArgumentNullException(
+ $"The connection string is missing the required '{name}' property.");
+ }
+
+ return value!;
+ }
+}
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
new file mode 100644
index 00000000..1ec6b3a7
--- /dev/null
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -0,0 +1,156 @@
+using Azure.Core;
+using Microsoft.DurableTask.Client;
+using Microsoft.DurableTask.Worker;
+using System.Diagnostics;
+
+namespace DurableTask.Extensions.Azure;
+
+// NOTE: These extension methods will eventually be provided by the Durable Task SDK itself.
+public static class DurableTaskSchedulerExtensions
+{
+ // Configure the Durable Task *Worker* to use the Durable Task Scheduler service with the specified options.
+ public static void UseDurableTaskScheduler(
+ this IDurableTaskWorkerBuilder builder,
+ string endpointAddress,
+ string taskHubName,
+ TokenCredential credential,
+ Action? configure = null)
+ {
+ DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
+
+ configure?.Invoke(options);
+
+ builder.UseGrpc(GetGrpcChannelForOptions(options));
+ }
+
+ public static void UseDurableTaskScheduler(
+ this IDurableTaskWorkerBuilder builder,
+ string connectionString,
+ Action? configure = null)
+ {
+ var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ configure?.Invoke(options);
+ builder.UseGrpc(GetGrpcChannelForOptions(options));
+ }
+
+ // Configure the Durable Task *Client* to use the Durable Task Scheduler service with the specified options.
+ public static void UseDurableTaskScheduler(
+ this IDurableTaskClientBuilder builder,
+ string endpointAddress,
+ string taskHubName,
+ TokenCredential credential,
+ Action? configure = null)
+ {
+ DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
+
+ configure?.Invoke(options);
+
+ builder.UseGrpc(GetGrpcChannelForOptions(options));
+ }
+
+ public static void UseDurableTaskScheduler(
+ this IDurableTaskClientBuilder builder,
+ string connectionString,
+ Action? configure = null)
+ {
+ var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ configure?.Invoke(options);
+ builder.UseGrpc(GetGrpcChannelForOptions(options));
+ }
+
+ static GrpcChannel GetGrpcChannelForOptions(DurableTaskSchedulerOptions options)
+ {
+ if (string.IsNullOrEmpty(options.EndpointAddress))
+ {
+ throw RequiredOptionMissing(nameof(options.TaskHubName));
+ }
+
+ if (string.IsNullOrEmpty(options.TaskHubName))
+ {
+ throw RequiredOptionMissing(nameof(options.TaskHubName));
+ }
+
+ TokenCredential credential = options.Credential ?? throw RequiredOptionMissing(nameof(options.Credential));
+
+ string taskHubName = options.TaskHubName;
+ string endpoint = options.EndpointAddress;
+
+ if (!endpoint.Contains("://"))
+ {
+ endpoint = $"https://{endpoint}";
+ }
+
+ string resourceId = options.ResourceId ?? "https://durabletask.io";
+#if NET6_0
+ int processId = Environment.ProcessId;
+#else
+ int processId = Process.GetCurrentProcess().Id;
+#endif
+ string workerId = options.WorkerId ?? $"{Environment.MachineName},{processId},{Guid.NewGuid()}";
+
+ TokenCache? cache =
+ options.Credential is not null
+ ? new(
+ options.Credential,
+ new(new[] { $"{options.ResourceId}/.default" }),
+ TimeSpan.FromMinutes(5))
+ : null;
+
+ CallCredentials managedBackendCreds = CallCredentials.FromInterceptor(
+ async (context, metadata) =>
+ {
+ metadata.Add("taskhub", taskHubName);
+ metadata.Add("workerid", workerId);
+
+ if (cache is null)
+ {
+ return;
+ }
+
+ AccessToken token = await cache.GetTokenAsync(context.CancellationToken);
+
+ metadata.Add("Authorization", $"Bearer {token.Token}");
+ });
+
+ #if NET6_0
+ return GrpcChannel.ForAddress(
+ endpoint,
+ new GrpcChannelOptions
+ {
+ Credentials = ChannelCredentials.Create(ChannelCredentials.SecureSsl, managedBackendCreds),
+ });
+ #else
+ return new GrpcChannel(
+ endpoint,
+ ChannelCredentials.Create(ChannelCredentials.SecureSsl, managedBackendCreds));
+ #endif
+ }
+
+ static Exception RequiredOptionMissing(string optionName)
+ {
+ return new ArgumentException(message: $"Required option '{optionName}' was not provided.");
+ }
+
+ sealed class TokenCache(TokenCredential credential, TokenRequestContext context, TimeSpan margin)
+ {
+ readonly TokenCredential credential = credential;
+ readonly TokenRequestContext context = context;
+ readonly TimeSpan margin = margin;
+
+ AccessToken? token;
+
+ public async Task GetTokenAsync(CancellationToken cancellationToken)
+ {
+ DateTimeOffset nowWithMargin = DateTimeOffset.UtcNow.Add(this.margin);
+
+ if (this.token is null
+ || this.token.Value.RefreshOn < nowWithMargin
+ || this.token.Value.ExpiresOn < nowWithMargin)
+ {
+ this.token = await this.credential.GetTokenAsync(this.context, cancellationToken);
+ }
+
+ return this.token.Value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
new file mode 100644
index 00000000..a089bcc9
--- /dev/null
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -0,0 +1,147 @@
+using System.Globalization;
+using Azure.Core;
+using Azure.Identity;
+
+namespace DurableTask.Extensions.Azure;
+
+// NOTE: These options definitions will eventually be provided by the Durable Task SDK itself.
+
+///
+/// Options for configuring the Durable Task Scheduler.
+///
+public class DurableTaskSchedulerOptions
+{
+ internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName, TokenCredential? credential = null)
+ {
+ this.EndpointAddress = endpointAddress ?? throw new ArgumentNullException(nameof(endpointAddress));
+ this.TaskHubName = taskHubName ?? throw new ArgumentNullException(nameof(taskHubName));
+ this.Credential = credential;
+ }
+
+ ///
+ /// The endpoint address of the Durable Task Scheduler resource.
+ /// Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".
+ ///
+ public string EndpointAddress { get; }
+
+ ///
+ /// The name of the task hub resource associated with the Durable Task Scheduler resource.
+ ///
+ public string TaskHubName { get; }
+
+ ///
+ /// The credential used to authenticate with the Durable Task Scheduler task hub resource.
+ ///
+ public TokenCredential? Credential { get; }
+
+ ///
+ /// The resource ID of the Durable Task Scheduler resource.
+ /// The default value is https://durabletask.io.
+ ///
+ public string? ResourceId { get; set; }
+
+ ///
+ /// The worker ID used to identify the worker instance.
+ /// The default value is a string containing the machine name and the process ID.
+ ///
+ public string? WorkerId { get; set; }
+
+
+ public static DurableTaskSchedulerOptions FromConnectionString(string connectionString)
+ {
+ return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
+ }
+
+ public static DurableTaskSchedulerOptions FromConnectionString(
+ DurableTaskSchedulerConnectionString connectionString)
+ {
+ // Example connection strings:
+ // "Endpoint=https://myaccount.westus3.durabletask.io/;Authentication=ManagedIdentity;ClientID=00000000-0000-0000-0000-000000000000;TaskHubName=th01"
+ // "Endpoint=https://myaccount.westus3.durabletask.io/;Authentication=DefaultAzure;TaskHubName=th01"
+ // "Endpoint=https://myaccount.westus3.durabletask.io/;Authentication=None;TaskHubName=th01" (undocumented and only intended for local testing)
+
+ string endpointAddress = connectionString.Endpoint;
+
+ if (!endpointAddress.Contains("://"))
+ {
+ // If the protocol is missing, assume HTTPS.
+ endpointAddress = "https://" + endpointAddress;
+ }
+
+ string authType = connectionString.Authentication;
+
+ TokenCredential? credential;
+
+ // Parse the supported auth types, in a case-insensitive way and without spaces
+ switch (authType.ToLower(CultureInfo.InvariantCulture).Replace(" ", string.Empty))
+ {
+ case "defaultazure":
+ // Default Azure credentials, suitable for a variety of scenarios
+ // In many cases, users will need to pass additional configuration options via env vars
+ credential = new DefaultAzureCredential();
+ break;
+
+ case "managedidentity":
+ // Use Managed identity
+ // Suitable for Azure-hosted scenarios
+ // Note that ClientId could be null for system-assigned managed identities
+ credential = new ManagedIdentityCredential(connectionString.ClientId);
+ break;
+
+ case "workloadidentity":
+ // Use Workload Identity Federation.
+ // This is commonly-used in Kubernetes (hosted on Azure or anywhere), or in CI environments like
+ // Azure Pipelines or GitHub Actions. It can also be used with SPIFFE.
+ WorkloadIdentityCredentialOptions opts = new() { };
+ if (!string.IsNullOrEmpty(connectionString.ClientId))
+ {
+ opts.ClientId = connectionString.ClientId;
+ }
+
+ if (!string.IsNullOrEmpty(connectionString.TenantId))
+ {
+ opts.TenantId = connectionString.TenantId;
+ }
+
+ if (connectionString.AdditionallyAllowedTenants is not null)
+ {
+ foreach (string tenant in connectionString.AdditionallyAllowedTenants)
+ {
+ opts.AdditionallyAllowedTenants.Add(tenant);
+ }
+ }
+
+ credential = new WorkloadIdentityCredential(opts);
+ break;
+
+ case "environment":
+ // Use credentials from the environment
+ credential = new EnvironmentCredential();
+ break;
+
+ case "azurecli":
+ // Use credentials from the Azure CLI
+ credential = new AzureCliCredential();
+ break;
+
+ case "azurepowershell":
+ // Use credentials from the Azure PowerShell modules
+ credential = new AzurePowerShellCredential();
+ break;
+
+ case "none":
+ // Do not use any authentication/authorization (for testing only)
+ // This is a no-op
+ credential = null;
+ break;
+
+ default:
+ throw new ArgumentException(
+ $"The connection string contains an unsupported authentication type '{authType}'.",
+ nameof(connectionString));
+ }
+
+ DurableTaskSchedulerOptions options = new(endpointAddress, connectionString.TaskHubName, credential);
+ return options;
+ }
+}
\ No newline at end of file
diff --git a/test/Extensions.Azure.Tests/DurableTaskSchedulerConnectionStringTests.cs b/test/Extensions.Azure.Tests/DurableTaskSchedulerConnectionStringTests.cs
new file mode 100644
index 00000000..bb56ad55
--- /dev/null
+++ b/test/Extensions.Azure.Tests/DurableTaskSchedulerConnectionStringTests.cs
@@ -0,0 +1,122 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using FluentAssertions;
+using System.Data.Common;
+using Xunit;
+
+namespace DurableTask.Extensions.Azure.Tests;
+
+public class DurableTaskSchedulerConnectionStringTests
+{
+ private const string ValidEndpoint = "myaccount.westus3.durabletask.io";
+ private const string ValidTaskHub = "testhub";
+ private const string ValidClientId = "00000000-0000-0000-0000-000000000000";
+ private const string ValidTenantId = "11111111-1111-1111-1111-111111111111";
+
+ [Fact]
+ public void Constructor_WithValidConnectionString_ShouldParseCorrectly()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
+
+ // Act
+ var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+
+ // Assert
+ parsedConnectionString.Endpoint.Should().Be(ValidEndpoint);
+ parsedConnectionString.TaskHubName.Should().Be(ValidTaskHub);
+ parsedConnectionString.Authentication.Should().Be("DefaultAzure");
+ }
+
+ [Fact]
+ public void Constructor_WithManagedIdentity_ShouldParseClientId()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=ManagedIdentity;ClientID={ValidClientId};TaskHub={ValidTaskHub}";
+
+ // Act
+ var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+
+ // Assert
+ parsedConnectionString.ClientId.Should().Be(ValidClientId);
+ }
+
+ [Fact]
+ public void Constructor_WithWorkloadIdentity_ShouldParseAllProperties()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=WorkloadIdentity;ClientID={ValidClientId};TenantId={ValidTenantId};TaskHub={ValidTaskHub}";
+
+ // Act
+ var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+
+ // Assert
+ parsedConnectionString.ClientId.Should().Be(ValidClientId);
+ parsedConnectionString.TenantId.Should().Be(ValidTenantId);
+ }
+
+ [Fact]
+ public void Constructor_WithAdditionallyAllowedTenants_ShouldParseTenantList()
+ {
+ // Arrange
+ const string tenants = "tenant1,tenant2,tenant3";
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=WorkloadIdentity;AdditionallyAllowedTenants={tenants};TaskHub={ValidTaskHub}";
+
+ // Act
+ var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+
+ // Assert
+ parsedConnectionString.AdditionallyAllowedTenants.Should().NotBeNull();
+ parsedConnectionString.AdditionallyAllowedTenants.Should().BeEquivalentTo(new[] { "tenant1", "tenant2", "tenant3" });
+ }
+
+ [Fact]
+ public void Constructor_WithMissingRequiredProperties_ShouldThrowArgumentNullException()
+ {
+ // Arrange
+ string connectionString = $"Authentication=DefaultAzure;TaskHub={ValidTaskHub}"; // Missing Endpoint
+
+ // Act & Assert
+ var action = () => _ = new DurableTaskSchedulerConnectionString(connectionString).Endpoint;
+ var exception = action.Should().Throw().Which;
+ exception.Message.Should().Contain("'Endpoint' property");
+ }
+
+ [Fact]
+ public void Constructor_WithInvalidConnectionString_ShouldThrowArgumentException()
+ {
+ // Arrange
+ string connectionString = "This is not a valid connection string";
+
+ // Act & Assert
+ var action = () => new DurableTaskSchedulerConnectionString(connectionString);
+ action.Should().Throw()
+ .WithMessage("*Format of the initialization string does not conform to specification*");
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(null)]
+ public void Constructor_WithNullOrEmptyConnectionString_ShouldThrowArgumentException(string? connectionString)
+ {
+ // Act & Assert
+ var action = () => _ = new DurableTaskSchedulerConnectionString(connectionString!).Endpoint;
+ action.Should().Throw()
+ .WithMessage("*'Endpoint' property*");
+ }
+
+ [Fact]
+ public void GetValue_WithNonExistentProperty_ShouldReturnNull()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
+ var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+
+ // Assert
+ parsedConnectionString.ClientId.Should().BeNull();
+ parsedConnectionString.TenantId.Should().BeNull();
+ parsedConnectionString.TokenFilePath.Should().BeNull();
+ parsedConnectionString.AdditionallyAllowedTenants.Should().BeNull();
+ }
+}
diff --git a/test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs b/test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs
new file mode 100644
index 00000000..440cab0f
--- /dev/null
+++ b/test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs
@@ -0,0 +1,178 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Azure.Core;
+using Azure.Identity;
+using FluentAssertions;
+using Grpc.Net.Client;
+using Microsoft.DurableTask.Client;
+using Microsoft.DurableTask.Client.Grpc;
+using Microsoft.DurableTask.Worker;
+using Microsoft.DurableTask.Worker.Grpc;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Moq;
+using Xunit;
+
+namespace DurableTask.Extensions.Azure.Tests;
+
+public class DurableTaskSchedulerExtensionsTests
+{
+ private const string ValidEndpoint = "myaccount.westus3.durabletask.io";
+ private const string ValidTaskHub = "testhub";
+
+ [Fact]
+ public void UseDurableTaskScheduler_Worker_WithEndpointAndCredential_ShouldConfigureCorrectly()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ var credential = new DefaultAzureCredential();
+
+ // Act
+ mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential);
+
+ // Assert - Verify that the options were registered
+ var provider = services.BuildServiceProvider();
+ var options = provider.GetService>();
+ options.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void UseDurableTaskScheduler_Worker_WithConnectionString_ShouldConfigureCorrectly()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
+
+ // Act
+ mockBuilder.Object.UseDurableTaskScheduler(connectionString);
+
+ // Assert
+ var provider = services.BuildServiceProvider();
+ var options = provider.GetService>();
+ options.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void UseDurableTaskScheduler_Client_WithEndpointAndCredential_ShouldConfigureCorrectly()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ var credential = new DefaultAzureCredential();
+
+ // Act
+ mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential);
+
+ // Assert
+ var provider = services.BuildServiceProvider();
+ var options = provider.GetService>();
+ options.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void UseDurableTaskScheduler_Client_WithConnectionString_ShouldConfigureCorrectly()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
+
+ // Act
+ mockBuilder.Object.UseDurableTaskScheduler(connectionString);
+
+ // Assert
+ var provider = services.BuildServiceProvider();
+ var options = provider.GetService>();
+ options.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void UseDurableTaskScheduler_WithOptions_ShouldApplyConfiguration()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ var credential = new DefaultAzureCredential();
+ string workerId = "customWorker";
+
+ // Act
+ mockBuilder.Object.UseDurableTaskScheduler(
+ ValidEndpoint,
+ ValidTaskHub,
+ credential,
+ options => options.WorkerId = workerId);
+
+ // Assert
+ var provider = services.BuildServiceProvider();
+ var options = provider.GetService>();
+ options.Should().NotBeNull();
+ }
+
+ [Theory]
+ [InlineData(null, ValidTaskHub)]
+ [InlineData(ValidEndpoint, null)]
+ public void UseDurableTaskScheduler_WithNullParameters_ShouldThrowArgumentNullException(string endpoint, string taskHub)
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ var credential = new DefaultAzureCredential();
+
+ // Act & Assert
+ var action = () => mockBuilder.Object.UseDurableTaskScheduler(endpoint, taskHub, credential);
+ action.Should().Throw();
+ }
+
+ [Fact]
+ public void UseDurableTaskScheduler_WithNullCredential_ShouldThrowArgumentException()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ TokenCredential? credential = null;
+
+ // Act & Assert
+ var action = () => mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential!);
+ action.Should().Throw()
+ .WithMessage("*Required option 'Credential' was not provided*");
+ }
+
+ [Fact]
+ public void UseDurableTaskScheduler_WithInvalidConnectionString_ShouldThrowArgumentException()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ string invalidConnectionString = "This is not a valid connection string";
+
+ // Act & Assert
+ var action = () => mockBuilder.Object.UseDurableTaskScheduler(invalidConnectionString);
+ action.Should().Throw();
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(null)]
+ public void UseDurableTaskScheduler_WithNullOrEmptyConnectionString_ShouldThrowArgumentException(string connectionString)
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+
+ // Act & Assert
+ var action = () => mockBuilder.Object.UseDurableTaskScheduler(connectionString);
+ action.Should().Throw();
+ }
+}
diff --git a/test/Extensions.Azure.Tests/DurableTaskSchedulerOptionsTests.cs b/test/Extensions.Azure.Tests/DurableTaskSchedulerOptionsTests.cs
new file mode 100644
index 00000000..c7e8fd0a
--- /dev/null
+++ b/test/Extensions.Azure.Tests/DurableTaskSchedulerOptionsTests.cs
@@ -0,0 +1,119 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Azure.Core;
+using Azure.Identity;
+using FluentAssertions;
+using Xunit;
+
+namespace DurableTask.Extensions.Azure.Tests;
+
+public class DurableTaskSchedulerOptionsTests
+{
+ private const string ValidEndpoint = "myaccount.westus3.durabletask.io";
+ private const string ValidTaskHub = "testhub";
+
+ [Fact]
+ public void FromConnectionString_WithDefaultAzure_ShouldCreateValidInstance()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
+
+ // Act
+ var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+
+ // Assert
+ options.EndpointAddress.Should().Be($"https://{ValidEndpoint}");
+ options.TaskHubName.Should().Be(ValidTaskHub);
+ options.Credential.Should().BeOfType();
+ }
+
+ [Fact]
+ public void FromConnectionString_WithManagedIdentity_ShouldCreateValidInstance()
+ {
+ // Arrange
+ const string clientId = "00000000-0000-0000-0000-000000000000";
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=ManagedIdentity;ClientID={clientId};TaskHub={ValidTaskHub}";
+
+ // Act
+ var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+
+ // Assert
+ options.EndpointAddress.Should().Be($"https://{ValidEndpoint}");
+ options.TaskHubName.Should().Be(ValidTaskHub);
+ options.Credential.Should().BeOfType();
+ }
+
+ [Fact]
+ public void FromConnectionString_WithWorkloadIdentity_ShouldCreateValidInstance()
+ {
+ // Arrange
+ const string clientId = "00000000-0000-0000-0000-000000000000";
+ const string tenantId = "11111111-1111-1111-1111-111111111111";
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=WorkloadIdentity;ClientID={clientId};TenantId={tenantId};TaskHub={ValidTaskHub}";
+
+ // Act
+ var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+
+ // Assert
+ options.EndpointAddress.Should().Be($"https://{ValidEndpoint}");
+ options.TaskHubName.Should().Be(ValidTaskHub);
+ options.Credential.Should().BeOfType();
+ }
+
+ [Theory]
+ [InlineData("Environment")]
+ [InlineData("AzureCLI")]
+ [InlineData("AzurePowerShell")]
+ public void FromConnectionString_WithValidAuthTypes_ShouldCreateValidInstance(string authType)
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication={authType};TaskHub={ValidTaskHub}";
+
+ // Act
+ var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+
+ // Assert
+ options.EndpointAddress.Should().Be($"https://{ValidEndpoint}");
+ options.TaskHubName.Should().Be(ValidTaskHub);
+ options.Credential.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void FromConnectionString_WithInvalidAuthType_ShouldThrowArgumentException()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=InvalidAuth;TaskHub={ValidTaskHub}";
+
+ // Act & Assert
+ var action = () => DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ action.Should().Throw()
+ .WithMessage("*contains an unsupported authentication type*");
+ }
+
+ [Fact]
+ public void FromConnectionString_WithMissingRequiredProperties_ShouldThrowArgumentNullException()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure"; // Missing TaskHub
+
+ // Act & Assert
+ var action = () => DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ action.Should().Throw();
+ }
+
+ [Fact]
+ public void FromConnectionString_WithNone_ShouldCreateInstanceWithNullCredential()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=None;TaskHub={ValidTaskHub}";
+
+ // Act
+ var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+
+ // Assert
+ options.EndpointAddress.Should().Be($"https://{ValidEndpoint}");
+ options.TaskHubName.Should().Be(ValidTaskHub);
+ options.Credential.Should().BeNull();
+ }
+}
diff --git a/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj b/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj
new file mode 100644
index 00000000..8ea58bf8
--- /dev/null
+++ b/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj
@@ -0,0 +1,34 @@
+
+
+
+ net6.0
+ enable
+ enable
+ false
+ true
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
From 6f1d2bc9111819a941be34501d094dec9ddda2b3 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Wed, 8 Jan 2025 22:46:31 -0800
Subject: [PATCH 02/58] support local conn with no auth and via http
---
.../Azure/DurableTaskSchedulerExtensions.cs | 30 ++++++++++++++-----
1 file changed, 23 insertions(+), 7 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 1ec6b3a7..71c81877 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -70,8 +70,6 @@ static GrpcChannel GetGrpcChannelForOptions(DurableTaskSchedulerOptions options)
throw RequiredOptionMissing(nameof(options.TaskHubName));
}
- TokenCredential credential = options.Credential ?? throw RequiredOptionMissing(nameof(options.Credential));
-
string taskHubName = options.TaskHubName;
string endpoint = options.EndpointAddress;
@@ -112,17 +110,35 @@ options.Credential is not null
metadata.Add("Authorization", $"Bearer {token.Token}");
});
+ // Production will use HTTPS, but local testing will use HTTP
+ ChannelCredentials channelCreds = endpoint.StartsWith("https://") ?
+ ChannelCredentials.SecureSsl :
+ ChannelCredentials.Insecure;
#if NET6_0
- return GrpcChannel.ForAddress(
- endpoint,
- new GrpcChannelOptions
+ return GrpcChannel.ForAddress(this.options.Address, new GrpcChannelOptions
{
- Credentials = ChannelCredentials.Create(ChannelCredentials.SecureSsl, managedBackendCreds),
+ // The same credential is being used for all operations.
+ // https://learn.microsoft.com/aspnet/core/grpc/authn-and-authz#set-the-bearer-token-with-callcredentials
+ Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
+
+ // TODO: This is not appropriate for use in production settings. Setting this to true should
+ // only be done for local testing. We should hide this setting behind some kind of flag.
+ UnsafeUseInsecureChannelCallCredentials = true,
});
#else
return new GrpcChannel(
endpoint,
- ChannelCredentials.Create(ChannelCredentials.SecureSsl, managedBackendCreds));
+ ChannelCredentials.Create(channelCreds, managedBackendCreds),
+ new GrpcChannelOptions
+ {
+ // The same credential is being used for all operations.
+ // https://learn.microsoft.com/aspnet/core/grpc/authn-and-authz#set-the-bearer-token-with-callcredentials
+ Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
+
+ // TODO: This is not appropriate for use in production settings. Setting this to true should
+ // only be done for local testing. We should hide this setting behind some kind of flag.
+ UnsafeUseInsecureChannelCallCredentials = true,
+ });
#endif
}
From 51e39bde6e8987b6b9c444fe84ca86cecc1ddf9a Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Wed, 8 Jan 2025 22:55:07 -0800
Subject: [PATCH 03/58] be consistent with azuremanaged targetversion
---
src/Extensions/Azure/Azure.csproj | 2 +-
.../Azure/DurableTaskSchedulerExtensions.cs | 25 +++----------------
2 files changed, 4 insertions(+), 23 deletions(-)
diff --git a/src/Extensions/Azure/Azure.csproj b/src/Extensions/Azure/Azure.csproj
index 8f1d2e35..25704be0 100644
--- a/src/Extensions/Azure/Azure.csproj
+++ b/src/Extensions/Azure/Azure.csproj
@@ -1,7 +1,7 @@
- netstandard2.0;net6.0
+ net6.0
Azure extensions for the Durable Task Framework.
true
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 71c81877..0933921b 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -1,4 +1,5 @@
using Azure.Core;
+using Grpc.Net.Client;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Worker;
using System.Diagnostics;
@@ -79,12 +80,8 @@ static GrpcChannel GetGrpcChannelForOptions(DurableTaskSchedulerOptions options)
}
string resourceId = options.ResourceId ?? "https://durabletask.io";
-#if NET6_0
int processId = Environment.ProcessId;
-#else
- int processId = Process.GetCurrentProcess().Id;
-#endif
- string workerId = options.WorkerId ?? $"{Environment.MachineName},{processId},{Guid.NewGuid()}";
+ string workerId = options.WorkerId ?? $"{Environment.MachineName},{processId},{Guid.NewGuid():N}";
TokenCache? cache =
options.Credential is not null
@@ -114,8 +111,7 @@ options.Credential is not null
ChannelCredentials channelCreds = endpoint.StartsWith("https://") ?
ChannelCredentials.SecureSsl :
ChannelCredentials.Insecure;
- #if NET6_0
- return GrpcChannel.ForAddress(this.options.Address, new GrpcChannelOptions
+ return GrpcChannel.ForAddress(options.EndpointAddress, new GrpcChannelOptions
{
// The same credential is being used for all operations.
// https://learn.microsoft.com/aspnet/core/grpc/authn-and-authz#set-the-bearer-token-with-callcredentials
@@ -125,21 +121,6 @@ options.Credential is not null
// only be done for local testing. We should hide this setting behind some kind of flag.
UnsafeUseInsecureChannelCallCredentials = true,
});
- #else
- return new GrpcChannel(
- endpoint,
- ChannelCredentials.Create(channelCreds, managedBackendCreds),
- new GrpcChannelOptions
- {
- // The same credential is being used for all operations.
- // https://learn.microsoft.com/aspnet/core/grpc/authn-and-authz#set-the-bearer-token-with-callcredentials
- Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
-
- // TODO: This is not appropriate for use in production settings. Setting this to true should
- // only be done for local testing. We should hide this setting behind some kind of flag.
- UnsafeUseInsecureChannelCallCredentials = true,
- });
- #endif
}
static Exception RequiredOptionMissing(string optionName)
From 6987f1bcef3e0a26813c5de33410cf2d4aac9125 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Wed, 8 Jan 2025 22:56:39 -0800
Subject: [PATCH 04/58] clean
---
src/Extensions/Azure/DurableTaskSchedulerExtensions.cs | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 0933921b..395e8263 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -1,12 +1,9 @@
using Azure.Core;
-using Grpc.Net.Client;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Worker;
-using System.Diagnostics;
namespace DurableTask.Extensions.Azure;
-// NOTE: These extension methods will eventually be provided by the Durable Task SDK itself.
public static class DurableTaskSchedulerExtensions
{
// Configure the Durable Task *Worker* to use the Durable Task Scheduler service with the specified options.
From e1628154aa884420b0de30bab2a07050c7f25dfe Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Wed, 8 Jan 2025 23:01:07 -0800
Subject: [PATCH 05/58] namespace
---
src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs | 2 +-
src/Extensions/Azure/DurableTaskSchedulerExtensions.cs | 2 +-
src/Extensions/Azure/DurableTaskSchedulerOptions.cs | 2 +-
.../DurableTaskSchedulerConnectionStringTests.cs | 2 +-
.../DurableTaskSchedulerExtensionsTests.cs | 2 +-
test/Extensions.Azure.Tests/DurableTaskSchedulerOptionsTests.cs | 2 +-
6 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs b/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
index ae71cf21..04a7ad65 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
@@ -4,7 +4,7 @@
using System.Data.Common;
-namespace DurableTask.Extensions.Azure;
+namespace Microsoft.DurableTask.Extensions.Azure;
///
/// Represents the constituent parts of a connection string for a Durable Task Scheduler service.
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 395e8263..39ef3b22 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -2,7 +2,7 @@
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Worker;
-namespace DurableTask.Extensions.Azure;
+namespace Microsoft.DurableTask.Extensions.Azure;
public static class DurableTaskSchedulerExtensions
{
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index a089bcc9..4cc5c691 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -2,7 +2,7 @@
using Azure.Core;
using Azure.Identity;
-namespace DurableTask.Extensions.Azure;
+namespace Microsoft.DurableTask.Extensions.Azure;
// NOTE: These options definitions will eventually be provided by the Durable Task SDK itself.
diff --git a/test/Extensions.Azure.Tests/DurableTaskSchedulerConnectionStringTests.cs b/test/Extensions.Azure.Tests/DurableTaskSchedulerConnectionStringTests.cs
index bb56ad55..aaca0a66 100644
--- a/test/Extensions.Azure.Tests/DurableTaskSchedulerConnectionStringTests.cs
+++ b/test/Extensions.Azure.Tests/DurableTaskSchedulerConnectionStringTests.cs
@@ -5,7 +5,7 @@
using System.Data.Common;
using Xunit;
-namespace DurableTask.Extensions.Azure.Tests;
+namespace Microsoft.DurableTask.Extensions.Azure.Tests;
public class DurableTaskSchedulerConnectionStringTests
{
diff --git a/test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs b/test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs
index 440cab0f..24db69ca 100644
--- a/test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs
+++ b/test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs
@@ -14,7 +14,7 @@
using Moq;
using Xunit;
-namespace DurableTask.Extensions.Azure.Tests;
+namespace Microsoft.DurableTask.Extensions.Azure.Tests;
public class DurableTaskSchedulerExtensionsTests
{
diff --git a/test/Extensions.Azure.Tests/DurableTaskSchedulerOptionsTests.cs b/test/Extensions.Azure.Tests/DurableTaskSchedulerOptionsTests.cs
index c7e8fd0a..b1c903c2 100644
--- a/test/Extensions.Azure.Tests/DurableTaskSchedulerOptionsTests.cs
+++ b/test/Extensions.Azure.Tests/DurableTaskSchedulerOptionsTests.cs
@@ -6,7 +6,7 @@
using FluentAssertions;
using Xunit;
-namespace DurableTask.Extensions.Azure.Tests;
+namespace Microsoft.DurableTask.Extensions.Azure.Tests;
public class DurableTaskSchedulerOptionsTests
{
From e19de446b996da4965fa537178c646ec79e3a3e9 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Wed, 8 Jan 2025 23:02:42 -0800
Subject: [PATCH 06/58] fix
---
src/Extensions/Azure/DurableTaskSchedulerExtensions.cs | 6 +++++-
src/Extensions/Azure/DurableTaskSchedulerOptions.cs | 6 +++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 39ef3b22..1dce80d0 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -1,4 +1,8 @@
-using Azure.Core;
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.All rights reserved.
+// ------------------------------------------------------------
+
+using Azure.Core;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Worker;
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index 4cc5c691..e58e0c25 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -1,4 +1,8 @@
-using System.Globalization;
+// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.All rights reserved.
+// ------------------------------------------------------------
+
+using System.Globalization;
using Azure.Core;
using Azure.Identity;
From 3a6dc52aaaa636924033c6e57b995837a785582c Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Thu, 9 Jan 2025 00:36:10 -0800
Subject: [PATCH 07/58] fix
---
src/Extensions/Azure/DurableTaskSchedulerExtensions.cs | 10 +++-------
.../DurableTaskSchedulerExtensionsTests.cs | 5 ++---
2 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 1dce80d0..4a9638b6 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -19,9 +19,7 @@ public static void UseDurableTaskScheduler(
Action? configure = null)
{
DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
-
configure?.Invoke(options);
-
builder.UseGrpc(GetGrpcChannelForOptions(options));
}
@@ -44,9 +42,7 @@ public static void UseDurableTaskScheduler(
Action? configure = null)
{
DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
-
configure?.Invoke(options);
-
builder.UseGrpc(GetGrpcChannelForOptions(options));
}
@@ -64,7 +60,7 @@ static GrpcChannel GetGrpcChannelForOptions(DurableTaskSchedulerOptions options)
{
if (string.IsNullOrEmpty(options.EndpointAddress))
{
- throw RequiredOptionMissing(nameof(options.TaskHubName));
+ throw RequiredOptionMissing(nameof(options.EndpointAddress));
}
if (string.IsNullOrEmpty(options.TaskHubName))
@@ -88,7 +84,7 @@ static GrpcChannel GetGrpcChannelForOptions(DurableTaskSchedulerOptions options)
options.Credential is not null
? new(
options.Credential,
- new(new[] { $"{options.ResourceId}/.default" }),
+ new(new[] { $"{resourceId}/.default" }),
TimeSpan.FromMinutes(5))
: null;
@@ -112,7 +108,7 @@ options.Credential is not null
ChannelCredentials channelCreds = endpoint.StartsWith("https://") ?
ChannelCredentials.SecureSsl :
ChannelCredentials.Insecure;
- return GrpcChannel.ForAddress(options.EndpointAddress, new GrpcChannelOptions
+ return GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
{
// The same credential is being used for all operations.
// https://learn.microsoft.com/aspnet/core/grpc/authn-and-authz#set-the-bearer-token-with-callcredentials
diff --git a/test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs b/test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs
index 24db69ca..518b53c1 100644
--- a/test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs
+++ b/test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs
@@ -133,7 +133,7 @@ public void UseDurableTaskScheduler_WithNullParameters_ShouldThrowArgumentNullEx
}
[Fact]
- public void UseDurableTaskScheduler_WithNullCredential_ShouldThrowArgumentException()
+ public void UseDurableTaskScheduler_WithNullCredential_ShouldSucceed()
{
// Arrange
var services = new ServiceCollection();
@@ -143,8 +143,7 @@ public void UseDurableTaskScheduler_WithNullCredential_ShouldThrowArgumentExcept
// Act & Assert
var action = () => mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential!);
- action.Should().Throw()
- .WithMessage("*Required option 'Credential' was not provided*");
+ action.Should().NotThrow();
}
[Fact]
From 65dfb85bcae00e6ee2dec5c58ad707bf3de4297f Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Thu, 9 Jan 2025 00:44:42 -0800
Subject: [PATCH 08/58] doc
---
.../Azure/DurableTaskSchedulerExtensions.cs | 37 ++++++++++++++++++-
.../Azure/DurableTaskSchedulerOptions.cs | 15 ++++++--
2 files changed, 47 insertions(+), 5 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 4a9638b6..21ccf76e 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -8,9 +8,19 @@
namespace Microsoft.DurableTask.Extensions.Azure;
+///
+/// Extension methods for configuring Durable Task workers and clients to use the Azure Durable Task Scheduler service.
+///
public static class DurableTaskSchedulerExtensions
{
- // Configure the Durable Task *Worker* to use the Durable Task Scheduler service with the specified options.
+ ///
+ /// Configures Durable Task worker to use the Azure Durable Task Scheduler service.
+ ///
+ /// The worker builder to configure.
+ /// The endpoint address of the Durable Task Scheduler service.
+ /// The name of the task hub to connect to.
+ /// The credential to use for authentication.
+ /// Optional callback to configure additional options.
public static void UseDurableTaskScheduler(
this IDurableTaskWorkerBuilder builder,
string endpointAddress,
@@ -19,10 +29,18 @@ public static void UseDurableTaskScheduler(
Action? configure = null)
{
DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
+
configure?.Invoke(options);
+
builder.UseGrpc(GetGrpcChannelForOptions(options));
}
+ ///
+ /// Configures Durable Task worker to use the Azure Durable Task Scheduler service using a connection string.
+ ///
+ /// The worker builder to configure.
+ /// The connection string for the Durable Task Scheduler service.
+ /// Optional callback to configure additional options.
public static void UseDurableTaskScheduler(
this IDurableTaskWorkerBuilder builder,
string connectionString,
@@ -33,7 +51,14 @@ public static void UseDurableTaskScheduler(
builder.UseGrpc(GetGrpcChannelForOptions(options));
}
- // Configure the Durable Task *Client* to use the Durable Task Scheduler service with the specified options.
+ ///
+ /// Configures Durable Task client to use the Azure Durable Task Scheduler service.
+ ///
+ /// The client builder to configure.
+ /// The endpoint address of the Durable Task Scheduler service.
+ /// The name of the task hub to connect to.
+ /// The credential to use for authentication.
+ /// Optional callback to configure additional options.
public static void UseDurableTaskScheduler(
this IDurableTaskClientBuilder builder,
string endpointAddress,
@@ -42,10 +67,18 @@ public static void UseDurableTaskScheduler(
Action? configure = null)
{
DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
+
configure?.Invoke(options);
+
builder.UseGrpc(GetGrpcChannelForOptions(options));
}
+ ///
+ /// Configures Durable Task client to use the Azure Durable Task Scheduler service using a connection string.
+ ///
+ /// The client builder to configure.
+ /// The connection string for the Durable Task Scheduler service.
+ /// Optional callback to configure additional options.
public static void UseDurableTaskScheduler(
this IDurableTaskClientBuilder builder,
string connectionString,
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index e58e0c25..94bc63bc 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -8,8 +8,6 @@
namespace Microsoft.DurableTask.Extensions.Azure;
-// NOTE: These options definitions will eventually be provided by the Durable Task SDK itself.
-
///
/// Options for configuring the Durable Task Scheduler.
///
@@ -50,12 +48,23 @@ internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName,
///
public string? WorkerId { get; set; }
-
+ ///
+ /// Creates a new instance of from a connection string.
+ ///
+ /// The connection string containing the configuration settings.
+ /// A new instance of configured with the connection string settings.
+ /// Thrown when the connection string contains an unsupported authentication type.
public static DurableTaskSchedulerOptions FromConnectionString(string connectionString)
{
return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
}
+ ///
+ /// Creates a new instance of from a parsed connection string.
+ ///
+ /// The parsed connection string containing the configuration settings.
+ /// A new instance of configured with the connection string settings.
+ /// Thrown when the connection string contains an unsupported authentication type.
public static DurableTaskSchedulerOptions FromConnectionString(
DurableTaskSchedulerConnectionString connectionString)
{
From 29445a04f445a4f65256fa99b22eda10dd8ffe92 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Thu, 9 Jan 2025 01:06:41 -0800
Subject: [PATCH 09/58] ppl
---
.github/workflows/validate-build.yml | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/validate-build.yml b/.github/workflows/validate-build.yml
index 65153017..5f3694b4 100644
--- a/.github/workflows/validate-build.yml
+++ b/.github/workflows/validate-build.yml
@@ -25,7 +25,12 @@ jobs:
with:
submodules: true
- - name: Setup .NET
+ - name: Setup .NET 6.0
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: '6.0.x'
+
+ - name: Setup .NET from global.json
uses: actions/setup-dotnet@v3
with:
global-json-file: global.json
From e63f12a0a8ed38a410aa1346216f7caf2c6219bc Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Thu, 9 Jan 2025 01:15:36 -0800
Subject: [PATCH 10/58] remove
---
test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj | 4 ----
1 file changed, 4 deletions(-)
diff --git a/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj b/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj
index 8ea58bf8..a62bb109 100644
--- a/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj
+++ b/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj
@@ -2,10 +2,6 @@
net6.0
- enable
- enable
- false
- true
From 5c72ee7137d69384bc1a3560b3ac16c958142258 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Thu, 9 Jan 2025 01:20:13 -0800
Subject: [PATCH 11/58] test proj to sln
---
Microsoft.DurableTask.sln | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/Microsoft.DurableTask.sln b/Microsoft.DurableTask.sln
index 96d6355f..59acc82d 100644
--- a/Microsoft.DurableTask.sln
+++ b/Microsoft.DurableTask.sln
@@ -75,6 +75,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure", "src\Extensions\Azure\Azure.csproj", "{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Extensions.Azure.Tests", "test\Extensions.Azure.Tests\Extensions.Azure.Tests.csproj", "{DBB5DB4E-A1B0-4C86-A233-213789C46929}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -193,6 +195,10 @@ Global
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DBB5DB4E-A1B0-4C86-A233-213789C46929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DBB5DB4E-A1B0-4C86-A233-213789C46929}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DBB5DB4E-A1B0-4C86-A233-213789C46929}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DBB5DB4E-A1B0-4C86-A233-213789C46929}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -230,6 +236,7 @@ Global
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
{5227C712-2355-403F-90D6-51D0BCAE4D38} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC} = {5227C712-2355-403F-90D6-51D0BCAE4D38}
+ {DBB5DB4E-A1B0-4C86-A233-213789C46929} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
From c6e42c54c096ff7c1f8c0ddccff88be0fbd81823 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Thu, 9 Jan 2025 01:24:57 -0800
Subject: [PATCH 12/58] fix warning
---
.../DurableTaskSchedulerConnectionString.cs | 5 ++---
.../Azure/DurableTaskSchedulerExtensions.cs | 5 ++---
.../Azure/DurableTaskSchedulerOptions.cs | 18 ++++++++++--------
3 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs b/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
index 04a7ad65..4599cd64 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
@@ -1,6 +1,5 @@
-// ------------------------------------------------------------
-// Copyright (c) Microsoft Corporation.All rights reserved.
-// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
using System.Data.Common;
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 21ccf76e..6e933d41 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -1,6 +1,5 @@
-// ------------------------------------------------------------
-// Copyright (c) Microsoft Corporation.All rights reserved.
-// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
using Azure.Core;
using Microsoft.DurableTask.Client;
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index 94bc63bc..5e603515 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -1,6 +1,5 @@
-// ------------------------------------------------------------
-// Copyright (c) Microsoft Corporation.All rights reserved.
-// ------------------------------------------------------------
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
using System.Globalization;
using Azure.Core;
@@ -13,6 +12,9 @@ namespace Microsoft.DurableTask.Extensions.Azure;
///
public class DurableTaskSchedulerOptions
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName, TokenCredential? credential = null)
{
this.EndpointAddress = endpointAddress ?? throw new ArgumentNullException(nameof(endpointAddress));
@@ -21,29 +23,29 @@ internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName,
}
///
- /// The endpoint address of the Durable Task Scheduler resource.
+ /// Gets the endpoint address of the Durable Task Scheduler resource.
/// Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".
///
public string EndpointAddress { get; }
///
- /// The name of the task hub resource associated with the Durable Task Scheduler resource.
+ /// Gets the name of the task hub resource associated with the Durable Task Scheduler resource.
///
public string TaskHubName { get; }
///
- /// The credential used to authenticate with the Durable Task Scheduler task hub resource.
+ /// Gets the credential used to authenticate with the Durable Task Scheduler task hub resource.
///
public TokenCredential? Credential { get; }
///
- /// The resource ID of the Durable Task Scheduler resource.
+ /// Gets or sets the resource ID of the Durable Task Scheduler resource.
/// The default value is https://durabletask.io.
///
public string? ResourceId { get; set; }
///
- /// The worker ID used to identify the worker instance.
+ /// Gets or sets the worker ID used to identify the worker instance.
/// The default value is a string containing the machine name and the process ID.
///
public string? WorkerId { get; set; }
From 6a66aaa1f03bfa84a28e8db4376870a4168ba0ad Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Thu, 9 Jan 2025 01:35:00 -0800
Subject: [PATCH 13/58] remove dup
---
.../Extensions.Azure.Tests.csproj | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj b/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj
index a62bb109..e9c83d3e 100644
--- a/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj
+++ b/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj
@@ -5,19 +5,7 @@
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
From 131c575bed4a909e27d7ed9aaeb6ea79d9f804bd Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Thu, 9 Jan 2025 01:45:56 -0800
Subject: [PATCH 14/58] fix
---
.../Azure/DurableTaskSchedulerExtensions.cs | 24 ++++++++++---------
.../Azure/DurableTaskSchedulerOptions.cs | 9 +++----
2 files changed, 18 insertions(+), 15 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 6e933d41..1f1bfb02 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Azure.Core;
@@ -141,18 +141,20 @@ options.Credential is not null
ChannelCredentials.SecureSsl :
ChannelCredentials.Insecure;
return GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
- {
- // The same credential is being used for all operations.
- // https://learn.microsoft.com/aspnet/core/grpc/authn-and-authz#set-the-bearer-token-with-callcredentials
- Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
- // TODO: This is not appropriate for use in production settings. Setting this to true should
- // only be done for local testing. We should hide this setting behind some kind of flag.
- UnsafeUseInsecureChannelCallCredentials = true,
- });
+ return GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
+ {
+ // The same credential is being used for all operations.
+ // https://learn.microsoft.com/aspnet/core/grpc/authn-and-authz#set-the-bearer-token-with-callcredentials
+ Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
+
+ // TODO: This is not appropriate for use in production settings. Setting this to true should
+ // only be done for local testing. We should hide this setting behind some kind of flag.
+ UnsafeUseInsecureChannelCallCredentials = true,
+ });
}
- static Exception RequiredOptionMissing(string optionName)
+ static ArgumentException RequiredOptionMissing(string optionName)
{
return new ArgumentException(message: $"Required option '{optionName}' was not provided.");
}
@@ -179,4 +181,4 @@ public async Task GetTokenAsync(CancellationToken cancellationToken
return this.token.Value;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index 5e603515..96e7692d 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -1,5 +1,5 @@
// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
+// Licensed under the MIT License.
using System.Globalization;
using Azure.Core;
@@ -15,6 +15,9 @@ public class DurableTaskSchedulerOptions
///
/// Initializes a new instance of the class.
///
+ /// The endpoint address of the Durable Task Scheduler service.
+ /// The name of the task hub to connect to.
+ /// The credential to use for authentication, or null for no authentication.
internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName, TokenCredential? credential = null)
{
this.EndpointAddress = endpointAddress ?? throw new ArgumentNullException(nameof(endpointAddress));
@@ -74,7 +77,6 @@ public static DurableTaskSchedulerOptions FromConnectionString(
// "Endpoint=https://myaccount.westus3.durabletask.io/;Authentication=ManagedIdentity;ClientID=00000000-0000-0000-0000-000000000000;TaskHubName=th01"
// "Endpoint=https://myaccount.westus3.durabletask.io/;Authentication=DefaultAzure;TaskHubName=th01"
// "Endpoint=https://myaccount.westus3.durabletask.io/;Authentication=None;TaskHubName=th01" (undocumented and only intended for local testing)
-
string endpointAddress = connectionString.Endpoint;
if (!endpointAddress.Contains("://"))
@@ -84,7 +86,6 @@ public static DurableTaskSchedulerOptions FromConnectionString(
}
string authType = connectionString.Authentication;
-
TokenCredential? credential;
// Parse the supported auth types, in a case-insensitive way and without spaces
@@ -159,4 +160,4 @@ public static DurableTaskSchedulerOptions FromConnectionString(
DurableTaskSchedulerOptions options = new(endpointAddress, connectionString.TaskHubName, credential);
return options;
}
-}
\ No newline at end of file
+}
From 65fa60729223649af33f52db2ecd3c94c7771197 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Thu, 9 Jan 2025 01:57:27 -0800
Subject: [PATCH 15/58] Revert "fix"
This reverts commit 131c575bed4a909e27d7ed9aaeb6ea79d9f804bd.
---
.../Azure/DurableTaskSchedulerExtensions.cs | 24 +++++++++----------
.../Azure/DurableTaskSchedulerOptions.cs | 9 ++++---
2 files changed, 15 insertions(+), 18 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 1f1bfb02..6e933d41 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Azure.Core;
@@ -141,20 +141,18 @@ options.Credential is not null
ChannelCredentials.SecureSsl :
ChannelCredentials.Insecure;
return GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
+ {
+ // The same credential is being used for all operations.
+ // https://learn.microsoft.com/aspnet/core/grpc/authn-and-authz#set-the-bearer-token-with-callcredentials
+ Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
- return GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
- {
- // The same credential is being used for all operations.
- // https://learn.microsoft.com/aspnet/core/grpc/authn-and-authz#set-the-bearer-token-with-callcredentials
- Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
-
- // TODO: This is not appropriate for use in production settings. Setting this to true should
- // only be done for local testing. We should hide this setting behind some kind of flag.
- UnsafeUseInsecureChannelCallCredentials = true,
- });
+ // TODO: This is not appropriate for use in production settings. Setting this to true should
+ // only be done for local testing. We should hide this setting behind some kind of flag.
+ UnsafeUseInsecureChannelCallCredentials = true,
+ });
}
- static ArgumentException RequiredOptionMissing(string optionName)
+ static Exception RequiredOptionMissing(string optionName)
{
return new ArgumentException(message: $"Required option '{optionName}' was not provided.");
}
@@ -181,4 +179,4 @@ public async Task GetTokenAsync(CancellationToken cancellationToken
return this.token.Value;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index 96e7692d..5e603515 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -1,5 +1,5 @@
// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
+// Licensed under the MIT License.
using System.Globalization;
using Azure.Core;
@@ -15,9 +15,6 @@ public class DurableTaskSchedulerOptions
///
/// Initializes a new instance of the class.
///
- /// The endpoint address of the Durable Task Scheduler service.
- /// The name of the task hub to connect to.
- /// The credential to use for authentication, or null for no authentication.
internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName, TokenCredential? credential = null)
{
this.EndpointAddress = endpointAddress ?? throw new ArgumentNullException(nameof(endpointAddress));
@@ -77,6 +74,7 @@ public static DurableTaskSchedulerOptions FromConnectionString(
// "Endpoint=https://myaccount.westus3.durabletask.io/;Authentication=ManagedIdentity;ClientID=00000000-0000-0000-0000-000000000000;TaskHubName=th01"
// "Endpoint=https://myaccount.westus3.durabletask.io/;Authentication=DefaultAzure;TaskHubName=th01"
// "Endpoint=https://myaccount.westus3.durabletask.io/;Authentication=None;TaskHubName=th01" (undocumented and only intended for local testing)
+
string endpointAddress = connectionString.Endpoint;
if (!endpointAddress.Contains("://"))
@@ -86,6 +84,7 @@ public static DurableTaskSchedulerOptions FromConnectionString(
}
string authType = connectionString.Authentication;
+
TokenCredential? credential;
// Parse the supported auth types, in a case-insensitive way and without spaces
@@ -160,4 +159,4 @@ public static DurableTaskSchedulerOptions FromConnectionString(
DurableTaskSchedulerOptions options = new(endpointAddress, connectionString.TaskHubName, credential);
return options;
}
-}
+}
\ No newline at end of file
From 4f45ec553fdf83eb221cb0fadb576211b4298ddd Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 12:52:46 -0800
Subject: [PATCH 16/58] save
---
.../Azure/DurableTaskSchedulerExtensions.cs | 7 +------
src/Extensions/Azure/DurableTaskSchedulerOptions.cs | 12 +++++++++++-
2 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 6e933d41..d3809cfd 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Azure.Core;
@@ -103,11 +103,6 @@ static GrpcChannel GetGrpcChannelForOptions(DurableTaskSchedulerOptions options)
string taskHubName = options.TaskHubName;
string endpoint = options.EndpointAddress;
- if (!endpoint.Contains("://"))
- {
- endpoint = $"https://{endpoint}";
- }
-
string resourceId = options.ResourceId ?? "https://durabletask.io";
int processId = Environment.ProcessId;
string workerId = options.WorkerId ?? $"{Environment.MachineName},{processId},{Guid.NewGuid():N}";
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index 5e603515..aa3d2a1f 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -26,7 +26,17 @@ internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName,
/// Gets the endpoint address of the Durable Task Scheduler resource.
/// Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".
///
- public string EndpointAddress { get; }
+ public string EndpointAddress
+ {
+ get => this.endpointAddress;
+ set
+ {
+ // Add https:// prefix if no protocol is specified
+ this.endpointAddress = !value.Contains("://")
+ ? $"https://{value}"
+ : value;
+ }
+ }
///
/// Gets the name of the task hub resource associated with the Durable Task Scheduler resource.
From d4607e49208ba2e8687daca1efb96e1ace51244a Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 13:03:51 -0800
Subject: [PATCH 17/58] fix
---
.../Azure/DurableTaskSchedulerConnectionString.cs | 8 +-------
.../Azure/DurableTaskSchedulerExtensions.cs | 11 ++---------
2 files changed, 3 insertions(+), 16 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs b/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
index 4599cd64..9002f77b 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
@@ -70,12 +70,6 @@ public DurableTaskSchedulerConnectionString(string connectionString)
string GetRequiredValue(string name)
{
string? value = this.GetValue(name);
- if (string.IsNullOrEmpty(value))
- {
- throw new ArgumentNullException(
- $"The connection string is missing the required '{name}' property.");
- }
-
- return value!;
+ return Check.NotNullOrEmpty(value, $"The connection string is missing the required '{name}' property.");
}
}
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index d3809cfd..8fbce95c 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -90,15 +90,8 @@ public static void UseDurableTaskScheduler(
static GrpcChannel GetGrpcChannelForOptions(DurableTaskSchedulerOptions options)
{
- if (string.IsNullOrEmpty(options.EndpointAddress))
- {
- throw RequiredOptionMissing(nameof(options.EndpointAddress));
- }
-
- if (string.IsNullOrEmpty(options.TaskHubName))
- {
- throw RequiredOptionMissing(nameof(options.TaskHubName));
- }
+ Check.NotNullOrEmpty(options.EndpointAddress, nameof(options.EndpointAddress));
+ Check.NotNullOrEmpty(options.TaskHubName, nameof(options.TaskHubName));
string taskHubName = options.TaskHubName;
string endpoint = options.EndpointAddress;
From 552a9c83f136edd391b75fe4fa42f0b9b37b0576 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 13:44:16 -0800
Subject: [PATCH 18/58] save
---
.../Azure/DurableTaskSchedulerExtensions.cs | 83 +---------------
.../Azure/DurableTaskSchedulerOptions.cs | 99 ++++++++++++++++---
2 files changed, 90 insertions(+), 92 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 8fbce95c..4fda6df8 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -31,7 +31,7 @@ public static void UseDurableTaskScheduler(
configure?.Invoke(options);
- builder.UseGrpc(GetGrpcChannelForOptions(options));
+ builder.UseGrpc(options.GetGrpcChannel());
}
///
@@ -47,7 +47,7 @@ public static void UseDurableTaskScheduler(
{
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
configure?.Invoke(options);
- builder.UseGrpc(GetGrpcChannelForOptions(options));
+ builder.UseGrpc(options.GetGrpcChannel());
}
///
@@ -69,7 +69,7 @@ public static void UseDurableTaskScheduler(
configure?.Invoke(options);
- builder.UseGrpc(GetGrpcChannelForOptions(options));
+ builder.UseGrpc(options.GetGrpcChannel());
}
///
@@ -85,86 +85,11 @@ public static void UseDurableTaskScheduler(
{
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
configure?.Invoke(options);
- builder.UseGrpc(GetGrpcChannelForOptions(options));
- }
-
- static GrpcChannel GetGrpcChannelForOptions(DurableTaskSchedulerOptions options)
- {
- Check.NotNullOrEmpty(options.EndpointAddress, nameof(options.EndpointAddress));
- Check.NotNullOrEmpty(options.TaskHubName, nameof(options.TaskHubName));
-
- string taskHubName = options.TaskHubName;
- string endpoint = options.EndpointAddress;
-
- string resourceId = options.ResourceId ?? "https://durabletask.io";
- int processId = Environment.ProcessId;
- string workerId = options.WorkerId ?? $"{Environment.MachineName},{processId},{Guid.NewGuid():N}";
-
- TokenCache? cache =
- options.Credential is not null
- ? new(
- options.Credential,
- new(new[] { $"{resourceId}/.default" }),
- TimeSpan.FromMinutes(5))
- : null;
-
- CallCredentials managedBackendCreds = CallCredentials.FromInterceptor(
- async (context, metadata) =>
- {
- metadata.Add("taskhub", taskHubName);
- metadata.Add("workerid", workerId);
-
- if (cache is null)
- {
- return;
- }
-
- AccessToken token = await cache.GetTokenAsync(context.CancellationToken);
-
- metadata.Add("Authorization", $"Bearer {token.Token}");
- });
-
- // Production will use HTTPS, but local testing will use HTTP
- ChannelCredentials channelCreds = endpoint.StartsWith("https://") ?
- ChannelCredentials.SecureSsl :
- ChannelCredentials.Insecure;
- return GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
- {
- // The same credential is being used for all operations.
- // https://learn.microsoft.com/aspnet/core/grpc/authn-and-authz#set-the-bearer-token-with-callcredentials
- Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
-
- // TODO: This is not appropriate for use in production settings. Setting this to true should
- // only be done for local testing. We should hide this setting behind some kind of flag.
- UnsafeUseInsecureChannelCallCredentials = true,
- });
+ builder.UseGrpc(options.GetGrpcChannel());
}
static Exception RequiredOptionMissing(string optionName)
{
return new ArgumentException(message: $"Required option '{optionName}' was not provided.");
}
-
- sealed class TokenCache(TokenCredential credential, TokenRequestContext context, TimeSpan margin)
- {
- readonly TokenCredential credential = credential;
- readonly TokenRequestContext context = context;
- readonly TimeSpan margin = margin;
-
- AccessToken? token;
-
- public async Task GetTokenAsync(CancellationToken cancellationToken)
- {
- DateTimeOffset nowWithMargin = DateTimeOffset.UtcNow.Add(this.margin);
-
- if (this.token is null
- || this.token.Value.RefreshOn < nowWithMargin
- || this.token.Value.ExpiresOn < nowWithMargin)
- {
- this.token = await this.credential.GetTokenAsync(this.context, cancellationToken);
- }
-
- return this.token.Value;
- }
- }
}
\ No newline at end of file
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index aa3d2a1f..4151cbf2 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -4,6 +4,7 @@
using System.Globalization;
using Azure.Core;
using Azure.Identity;
+using Grpc.Core;
namespace Microsoft.DurableTask.Extensions.Azure;
@@ -17,8 +18,15 @@ public class DurableTaskSchedulerOptions
///
internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName, TokenCredential? credential = null)
{
- this.EndpointAddress = endpointAddress ?? throw new ArgumentNullException(nameof(endpointAddress));
- this.TaskHubName = taskHubName ?? throw new ArgumentNullException(nameof(taskHubName));
+ Check.NotNullOrEmpty(endpointAddress, nameof(endpointAddress));
+ Check.NotNullOrEmpty(taskHubName, nameof(taskHubName));
+
+ // Add https:// prefix if no protocol is specified
+ this.EndpointAddress = !endpointAddress.Contains("://")
+ ? $"https://{endpointAddress}"
+ : endpointAddress;
+
+ this.TaskHubName = taskHubName;
this.Credential = credential;
}
@@ -26,17 +34,7 @@ internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName,
/// Gets the endpoint address of the Durable Task Scheduler resource.
/// Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".
///
- public string EndpointAddress
- {
- get => this.endpointAddress;
- set
- {
- // Add https:// prefix if no protocol is specified
- this.endpointAddress = !value.Contains("://")
- ? $"https://{value}"
- : value;
- }
- }
+ public string EndpointAddress { get; }
///
/// Gets the name of the task hub resource associated with the Durable Task Scheduler resource.
@@ -71,6 +69,58 @@ public static DurableTaskSchedulerOptions FromConnectionString(string connection
return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
}
+ internal GrpcChannel GetGrpcChannel()
+ {
+ Check.NotNullOrEmpty(this.EndpointAddress, nameof(this.EndpointAddress));
+ Check.NotNullOrEmpty(this.TaskHubName, nameof(this.TaskHubName));
+
+ string taskHubName = this.TaskHubName;
+ string endpoint = this.EndpointAddress;
+
+ string resourceId = this.ResourceId ?? "https://durabletask.io";
+ int processId = Environment.ProcessId;
+ string workerId = this.WorkerId ?? $"{Environment.MachineName},{processId},{Guid.NewGuid():N}";
+
+ TokenCache? cache =
+ this.Credential is not null
+ ? new(
+ this.Credential,
+ new(new[] { $"{resourceId}/.default" }),
+ TimeSpan.FromMinutes(5))
+ : null;
+
+ CallCredentials managedBackendCreds = CallCredentials.FromInterceptor(
+ async (context, metadata) =>
+ {
+ metadata.Add("taskhub", taskHubName);
+ metadata.Add("workerid", workerId);
+
+ if (cache is null)
+ {
+ return;
+ }
+
+ AccessToken token = await cache.GetTokenAsync(context.CancellationToken);
+
+ metadata.Add("Authorization", $"Bearer {token.Token}");
+ });
+
+ // Production will use HTTPS, but local testing will use HTTP
+ ChannelCredentials channelCreds = endpoint.StartsWith("https://") ?
+ ChannelCredentials.SecureSsl :
+ ChannelCredentials.Insecure;
+ return GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
+ {
+ // The same credential is being used for all operations.
+ // https://learn.microsoft.com/aspnet/core/grpc/authn-and-authz#set-the-bearer-token-with-callcredentials
+ Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
+
+ // TODO: This is not appropriate for use in production settings. Setting this to true should
+ // only be done for local testing. We should hide this setting behind some kind of flag.
+ UnsafeUseInsecureChannelCallCredentials = true,
+ });
+ }
+
///
/// Creates a new instance of from a parsed connection string.
///
@@ -169,4 +219,27 @@ public static DurableTaskSchedulerOptions FromConnectionString(
DurableTaskSchedulerOptions options = new(endpointAddress, connectionString.TaskHubName, credential);
return options;
}
+
+ sealed class TokenCache(TokenCredential credential, TokenRequestContext context, TimeSpan margin)
+ {
+ readonly TokenCredential credential = credential;
+ readonly TokenRequestContext context = context;
+ readonly TimeSpan margin = margin;
+
+ AccessToken? token;
+
+ public async Task GetTokenAsync(CancellationToken cancellationToken)
+ {
+ DateTimeOffset nowWithMargin = DateTimeOffset.UtcNow.Add(this.margin);
+
+ if (this.token is null
+ || this.token.Value.RefreshOn < nowWithMargin
+ || this.token.Value.ExpiresOn < nowWithMargin)
+ {
+ this.token = await this.credential.GetTokenAsync(this.context, cancellationToken);
+ }
+
+ return this.token.Value;
+ }
+ }
}
\ No newline at end of file
From 54dba769f9face0ed18a1e4cbadd978e0e2aa9df Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 13:46:24 -0800
Subject: [PATCH 19/58] save
---
src/Extensions/Azure/DurableTaskSchedulerOptions.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index 4151cbf2..9bf17460 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -20,12 +20,12 @@ internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName,
{
Check.NotNullOrEmpty(endpointAddress, nameof(endpointAddress));
Check.NotNullOrEmpty(taskHubName, nameof(taskHubName));
-
+
// Add https:// prefix if no protocol is specified
- this.EndpointAddress = !endpointAddress.Contains("://")
- ? $"https://{endpointAddress}"
+ this.EndpointAddress = !endpointAddress.Contains("://")
+ ? $"https://{endpointAddress}"
: endpointAddress;
-
+
this.TaskHubName = taskHubName;
this.Credential = credential;
}
From 5e97555a2ac574a0fc350a15a003888227b6a311 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 13:57:32 -0800
Subject: [PATCH 20/58] some fb
---
src/Extensions/Azure/AccessTokenCache.cs | 50 +++++++++++++++++++
.../Azure/DurableTaskSchedulerOptions.cs | 34 ++-----------
2 files changed, 55 insertions(+), 29 deletions(-)
create mode 100644 src/Extensions/Azure/AccessTokenCache.cs
diff --git a/src/Extensions/Azure/AccessTokenCache.cs b/src/Extensions/Azure/AccessTokenCache.cs
new file mode 100644
index 00000000..0a273170
--- /dev/null
+++ b/src/Extensions/Azure/AccessTokenCache.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Azure.Core;
+
+namespace Microsoft.DurableTask.Extensions.Azure;
+
+///
+/// Caches and manages refresh for Azure access tokens.
+///
+internal sealed class AccessTokenCache
+{
+ readonly TokenCredential credential;
+ readonly TokenRequestContext context;
+ readonly TimeSpan margin;
+
+ AccessToken? token;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The token credential to use for authentication.
+ /// The token request context.
+ /// The time margin to use for token refresh.
+ public AccessTokenCache(TokenCredential credential, TokenRequestContext context, TimeSpan margin)
+ {
+ this.credential = credential;
+ this.context = context;
+ this.margin = margin;
+ }
+
+ ///
+ /// Gets a token, either from cache or by requesting a new one if needed.
+ ///
+ /// A cancellation token.
+ /// An access token.
+ public async Task GetTokenAsync(CancellationToken cancellationToken)
+ {
+ DateTimeOffset nowWithMargin = DateTimeOffset.UtcNow.Add(this.margin);
+
+ if (this.token is null
+ || this.token.Value.RefreshOn < nowWithMargin
+ || this.token.Value.ExpiresOn < nowWithMargin)
+ {
+ this.token = await this.credential.GetTokenAsync(this.context, cancellationToken);
+ }
+
+ return this.token.Value;
+ }
+}
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index 9bf17460..c7dc157d 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -81,11 +81,11 @@ internal GrpcChannel GetGrpcChannel()
int processId = Environment.ProcessId;
string workerId = this.WorkerId ?? $"{Environment.MachineName},{processId},{Guid.NewGuid():N}";
- TokenCache? cache =
+ AccessTokenCache? cache =
this.Credential is not null
- ? new(
+ ? new AccessTokenCache(
this.Credential,
- new(new[] { $"{resourceId}/.default" }),
+ new TokenRequestContext(new[] { $"{resourceId}/.default" }),
TimeSpan.FromMinutes(5))
: null;
@@ -95,13 +95,12 @@ this.Credential is not null
metadata.Add("taskhub", taskHubName);
metadata.Add("workerid", workerId);
- if (cache is null)
+ if (cache == null)
{
return;
}
-
+
AccessToken token = await cache.GetTokenAsync(context.CancellationToken);
-
metadata.Add("Authorization", $"Bearer {token.Token}");
});
@@ -219,27 +218,4 @@ public static DurableTaskSchedulerOptions FromConnectionString(
DurableTaskSchedulerOptions options = new(endpointAddress, connectionString.TaskHubName, credential);
return options;
}
-
- sealed class TokenCache(TokenCredential credential, TokenRequestContext context, TimeSpan margin)
- {
- readonly TokenCredential credential = credential;
- readonly TokenRequestContext context = context;
- readonly TimeSpan margin = margin;
-
- AccessToken? token;
-
- public async Task GetTokenAsync(CancellationToken cancellationToken)
- {
- DateTimeOffset nowWithMargin = DateTimeOffset.UtcNow.Add(this.margin);
-
- if (this.token is null
- || this.token.Value.RefreshOn < nowWithMargin
- || this.token.Value.ExpiresOn < nowWithMargin)
- {
- this.token = await this.credential.GetTokenAsync(this.context, cancellationToken);
- }
-
- return this.token.Value;
- }
- }
}
\ No newline at end of file
From 9c890c5a2b9d9c83dd7b5381ed649d929a57394a Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 14:05:03 -0800
Subject: [PATCH 21/58] fix
---
src/Extensions/Azure/DurableTaskSchedulerOptions.cs | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index c7dc157d..23d85bea 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -13,6 +13,8 @@ namespace Microsoft.DurableTask.Extensions.Azure;
///
public class DurableTaskSchedulerOptions
{
+ private readonly string defaultWorkerId;
+
///
/// Initializes a new instance of the class.
///
@@ -28,6 +30,9 @@ internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName,
this.TaskHubName = taskHubName;
this.Credential = credential;
+
+ // Generate the default worker ID once at construction time
+ this.defaultWorkerId = $"{Environment.MachineName},{Environment.ProcessId},{Guid.NewGuid():N}";
}
///
@@ -54,7 +59,7 @@ internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName,
///
/// Gets or sets the worker ID used to identify the worker instance.
- /// The default value is a string containing the machine name and the process ID.
+ /// The default value is a string containing the machine name, process ID, and a unique identifier.
///
public string? WorkerId { get; set; }
@@ -78,8 +83,7 @@ internal GrpcChannel GetGrpcChannel()
string endpoint = this.EndpointAddress;
string resourceId = this.ResourceId ?? "https://durabletask.io";
- int processId = Environment.ProcessId;
- string workerId = this.WorkerId ?? $"{Environment.MachineName},{processId},{Guid.NewGuid():N}";
+ string workerId = this.WorkerId ?? this.defaultWorkerId;
AccessTokenCache? cache =
this.Credential is not null
From 49c62822ede2637569e67bdc3274a93d626e27bb Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 14:06:15 -0800
Subject: [PATCH 22/58] update
---
src/Extensions/Azure/DurableTaskSchedulerOptions.cs | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index 23d85bea..3a0c6abd 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -32,6 +32,7 @@ internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName,
this.Credential = credential;
// Generate the default worker ID once at construction time
+ // TODO: More iteration needed over time https://github.com/microsoft/durabletask-dotnet/pull/362#discussion_r1909547102
this.defaultWorkerId = $"{Environment.MachineName},{Environment.ProcessId},{Guid.NewGuid():N}";
}
From 1adf7ccb85f017b246037d033ed950c8cbf26db8 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 14:16:28 -0800
Subject: [PATCH 23/58] update tests
---
Microsoft.DurableTask.sln | 2 +-
.../Azure/DurableTaskSchedulerExtensions.cs | 5 -----
.../Extensions.Azure.Tests.csproj | 18 ------------------
test/Extensions/Azure/Azure.Tests.csproj | 18 ++++++++++++++++++
...urableTaskSchedulerConnectionStringTests.cs | 0
.../DurableTaskSchedulerExtensionsTests.cs | 0
.../Azure}/DurableTaskSchedulerOptionsTests.cs | 0
7 files changed, 19 insertions(+), 24 deletions(-)
delete mode 100644 test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj
create mode 100644 test/Extensions/Azure/Azure.Tests.csproj
rename test/{Extensions.Azure.Tests => Extensions/Azure}/DurableTaskSchedulerConnectionStringTests.cs (100%)
rename test/{Extensions.Azure.Tests => Extensions/Azure}/DurableTaskSchedulerExtensionsTests.cs (100%)
rename test/{Extensions.Azure.Tests => Extensions/Azure}/DurableTaskSchedulerOptionsTests.cs (100%)
diff --git a/Microsoft.DurableTask.sln b/Microsoft.DurableTask.sln
index 59acc82d..4e5a4451 100644
--- a/Microsoft.DurableTask.sln
+++ b/Microsoft.DurableTask.sln
@@ -75,7 +75,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure", "src\Extensions\Azure\Azure.csproj", "{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Extensions.Azure.Tests", "test\Extensions.Azure.Tests\Extensions.Azure.Tests.csproj", "{DBB5DB4E-A1B0-4C86-A233-213789C46929}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Tests", "test\Extensions\Azure\Azure.Tests.csproj", "{DBB5DB4E-A1B0-4C86-A233-213789C46929}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 4fda6df8..ed823d29 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -87,9 +87,4 @@ public static void UseDurableTaskScheduler(
configure?.Invoke(options);
builder.UseGrpc(options.GetGrpcChannel());
}
-
- static Exception RequiredOptionMissing(string optionName)
- {
- return new ArgumentException(message: $"Required option '{optionName}' was not provided.");
- }
}
\ No newline at end of file
diff --git a/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj b/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj
deleted file mode 100644
index e9c83d3e..00000000
--- a/test/Extensions.Azure.Tests/Extensions.Azure.Tests.csproj
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
- net6.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/Extensions/Azure/Azure.Tests.csproj b/test/Extensions/Azure/Azure.Tests.csproj
new file mode 100644
index 00000000..53146103
--- /dev/null
+++ b/test/Extensions/Azure/Azure.Tests.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Extensions.Azure.Tests/DurableTaskSchedulerConnectionStringTests.cs b/test/Extensions/Azure/DurableTaskSchedulerConnectionStringTests.cs
similarity index 100%
rename from test/Extensions.Azure.Tests/DurableTaskSchedulerConnectionStringTests.cs
rename to test/Extensions/Azure/DurableTaskSchedulerConnectionStringTests.cs
diff --git a/test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs b/test/Extensions/Azure/DurableTaskSchedulerExtensionsTests.cs
similarity index 100%
rename from test/Extensions.Azure.Tests/DurableTaskSchedulerExtensionsTests.cs
rename to test/Extensions/Azure/DurableTaskSchedulerExtensionsTests.cs
diff --git a/test/Extensions.Azure.Tests/DurableTaskSchedulerOptionsTests.cs b/test/Extensions/Azure/DurableTaskSchedulerOptionsTests.cs
similarity index 100%
rename from test/Extensions.Azure.Tests/DurableTaskSchedulerOptionsTests.cs
rename to test/Extensions/Azure/DurableTaskSchedulerOptionsTests.cs
From 14b94ebdd51119d92110b9547ee221cf3568a565 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 15:24:38 -0800
Subject: [PATCH 24/58] sample
sample fix
---
Microsoft.DurableTask.sln | 7 +
.../dotnet/AspNetWebApp/AspNetWebApp.csproj | 27 ++++
.../dotnet/AspNetWebApp/DockerFile | 22 +++
.../Orchestrations/HelloCities.cs | 30 ++++
.../dotnet/AspNetWebApp/Program.cs | 55 +++++++
.../Properties/launchSettings.json | 23 +++
.../dotnet/AspNetWebApp/README.md | 147 ++++++++++++++++++
.../AspNetWebApp/ScenariosController.cs | 50 ++++++
.../portable-sdk/dotnet/AspNetWebApp/Utils.cs | 38 +++++
.../AspNetWebApp/appsettings.Development.json | 8 +
.../AspNetWebApp/appsettings.Production.json | 11 ++
.../dotnet/AspNetWebApp/appsettings.json | 9 ++
12 files changed, 427 insertions(+)
create mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/AspNetWebApp.csproj
create mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/DockerFile
create mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/Orchestrations/HelloCities.cs
create mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/Program.cs
create mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/Properties/launchSettings.json
create mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/README.md
create mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/ScenariosController.cs
create mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/Utils.cs
create mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Development.json
create mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Production.json
create mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/appsettings.json
diff --git a/Microsoft.DurableTask.sln b/Microsoft.DurableTask.sln
index 4e5a4451..b7758f81 100644
--- a/Microsoft.DurableTask.sln
+++ b/Microsoft.DurableTask.sln
@@ -77,6 +77,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure", "src\Extensions\Azu
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Tests", "test\Extensions\Azure\Azure.Tests.csproj", "{DBB5DB4E-A1B0-4C86-A233-213789C46929}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetWebApp", "samples\portable-sdk\dotnet\AspNetWebApp\AspNetWebApp.csproj", "{869D2D51-9372-4764-B059-C43B6C1180A3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -199,6 +201,10 @@ Global
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Release|Any CPU.Build.0 = Release|Any CPU
+ {869D2D51-9372-4764-B059-C43B6C1180A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {869D2D51-9372-4764-B059-C43B6C1180A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {869D2D51-9372-4764-B059-C43B6C1180A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {869D2D51-9372-4764-B059-C43B6C1180A3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -237,6 +243,7 @@ Global
{5227C712-2355-403F-90D6-51D0BCAE4D38} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC} = {5227C712-2355-403F-90D6-51D0BCAE4D38}
{DBB5DB4E-A1B0-4C86-A233-213789C46929} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
+ {869D2D51-9372-4764-B059-C43B6C1180A3} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/AspNetWebApp.csproj b/samples/portable-sdk/dotnet/AspNetWebApp/AspNetWebApp.csproj
new file mode 100644
index 00000000..14516742
--- /dev/null
+++ b/samples/portable-sdk/dotnet/AspNetWebApp/AspNetWebApp.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net8.0
+ enable
+ enable
+ true
+ $(BaseIntermediateOutputPath)Generated
+
+
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/DockerFile b/samples/portable-sdk/dotnet/AspNetWebApp/DockerFile
new file mode 100644
index 00000000..5ddb3ae9
--- /dev/null
+++ b/samples/portable-sdk/dotnet/AspNetWebApp/DockerFile
@@ -0,0 +1,22 @@
+#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+WORKDIR /app
+EXPOSE 8080
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+WORKDIR /src
+COPY ["AspNetWebApp.csproj", "."]
+RUN dotnet restore "./AspNetWebApp.csproj"
+COPY . .
+WORKDIR "/src/."
+RUN dotnet build "AspNetWebApp.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "AspNetWebApp.csproj" -c Release -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENV ASPNETCORE_ENVIRONMENT=Production
+ENTRYPOINT ["dotnet", "AspNetWebApp.dll"]
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/Orchestrations/HelloCities.cs b/samples/portable-sdk/dotnet/AspNetWebApp/Orchestrations/HelloCities.cs
new file mode 100644
index 00000000..5c48dcaa
--- /dev/null
+++ b/samples/portable-sdk/dotnet/AspNetWebApp/Orchestrations/HelloCities.cs
@@ -0,0 +1,30 @@
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Client;
+
+namespace AspNetWebApp.Scenarios;
+
+[DurableTask]
+class HelloCities : TaskOrchestrator>
+{
+ public override async Task> RunAsync(TaskOrchestrationContext context, string input)
+ {
+ List results =
+ [
+ await context.CallSayHelloAsync("Seattle"),
+ await context.CallSayHelloAsync("Amsterdam"),
+ await context.CallSayHelloAsync("Hyderabad"),
+ await context.CallSayHelloAsync("Shanghai"),
+ await context.CallSayHelloAsync("Tokyo"),
+ ];
+ return results;
+ }
+}
+
+[DurableTask]
+class SayHello : TaskActivity
+{
+ public override Task RunAsync(TaskActivityContext context, string cityName)
+ {
+ return Task.FromResult($"Hello, {cityName}!");
+ }
+}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/Program.cs b/samples/portable-sdk/dotnet/AspNetWebApp/Program.cs
new file mode 100644
index 00000000..4c21b7b2
--- /dev/null
+++ b/samples/portable-sdk/dotnet/AspNetWebApp/Program.cs
@@ -0,0 +1,55 @@
+using System.Text.Json.Serialization;
+using Azure.Core;
+using Azure.Identity;
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Client;
+using Microsoft.DurableTask.Worker;
+using Microsoft.DurableTask.Extensions.Azure;
+
+WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+
+string endpointAddress = builder.Configuration["DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS"]
+ ?? throw new InvalidOperationException("Missing required configuration 'DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS'");
+
+string taskHubName = builder.Configuration["DURABLE_TASK_SCHEDULER_TASK_HUB_NAME"]
+ ?? throw new InvalidOperationException("Missing required configuration 'DURABLE_TASK_SCHEDULER_TASK_HUB_NAME'");
+
+TokenCredential credential = builder.Environment.IsProduction()
+ ? new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = builder.Configuration["CONTAINER_APP_UMI_CLIENT_ID"] })
+ : new DefaultAzureCredential();
+
+// Add all the generated orchestrations and activities automatically
+builder.Services.AddDurableTaskWorker(builder =>
+{
+ builder.AddTasks(r => r.AddAllGeneratedTasks());
+ builder.UseDurableTaskScheduler(endpointAddress, taskHubName, credential);
+});
+
+// Register the client, which can be used to start orchestrations
+builder.Services.AddDurableTaskClient(builder =>
+{
+ builder.UseDurableTaskScheduler(endpointAddress, taskHubName, credential);
+});
+
+// Configure console logging using the simpler, more compact format
+builder.Services.AddLogging(logging =>
+{
+ logging.AddSimpleConsole(options =>
+ {
+ options.SingleLine = true;
+ options.UseUtcTimestamp = true;
+ options.TimestampFormat = "yyyy-MM-ddTHH:mm:ss.fffZ ";
+ });
+});
+
+// Configure the HTTP request pipeline
+builder.Services.AddControllers().AddJsonOptions(options =>
+{
+ options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
+ options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
+});
+
+// The actual listen URL can be configured in environment variables named "ASPNETCORE_URLS" or "ASPNETCORE_URLS_HTTPS"
+WebApplication app = builder.Build();
+app.MapControllers();
+app.Run();
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/Properties/launchSettings.json b/samples/portable-sdk/dotnet/AspNetWebApp/Properties/launchSettings.json
new file mode 100644
index 00000000..4ee2ec75
--- /dev/null
+++ b/samples/portable-sdk/dotnet/AspNetWebApp/Properties/launchSettings.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:36209",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://localhost:5008",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS": "https://localhost:8082",
+ "DURABLE_TASK_SCHEDULER_TASK_HUB_NAME": "samples"
+ }
+ }
+ }
+}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/README.md b/samples/portable-sdk/dotnet/AspNetWebApp/README.md
new file mode 100644
index 00000000..4875b63e
--- /dev/null
+++ b/samples/portable-sdk/dotnet/AspNetWebApp/README.md
@@ -0,0 +1,147 @@
+# Hello World with the Durable Task SDK for .NET
+
+In addition to [Durable Functions](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview), the [Durable Task SDK for .NET](https://github.com/microsoft/durabletask-dotnet) can also use the Durable Task Scheduler service for managing orchestration state.
+
+This directory includes a sample .NET console app that demonstrates how to use the Durable Task Scheduler with the Durable Task SDK for .NET (without any Azure Functions dependency).
+
+## Prerequisites
+
+- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
+- [PowerShell](https://docs.microsoft.com/powershell/scripting/install/installing-powershell)
+- [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli)
+
+## Creating a Durable Task Scheduler task hub
+
+Before you can run the app, you need to create a Durable Task Scheduler task hub in Azure and produce a connection string that references it.
+
+> **NOTE**: These are abbreviated instructions for simplicity. For a full set of instructions, see the Azure Durable Functions [QuickStart guide](../../../../quickstarts/HelloCities/README.md#create-a-durable-task-scheduler-namespace-and-task-hub).
+
+1. Install the Durable Task Scheduler CLI extension:
+
+ ```bash
+ az upgrade
+ az extension add --name durabletask --allow-preview true
+ ```
+
+1. Create a resource group:
+
+ ```powershell
+ az group create --name my-resource-group --location northcentralus
+ ```
+
+1. Create a Durable Task Scheduler namespace:
+
+ ```powershell
+ az durabletask namespace create -g my-resource-group --name my-namespace
+ ```
+
+1. Create a task hub within the namespace:
+
+ ```powershell
+ az durabletask taskhub create -g my-resource-group --namespace my-namespace --name "portable-dotnet"
+ ```
+
+1. Grant the current user permission to connect to the `portable-dotnet` task hub:
+
+ ```powershell
+ $subscriptionId = az account show --query "id" -o tsv
+ $loggedInUser = az account show --query "user.name" -o tsv
+
+ az role assignment create `
+ --assignee $loggedInUser `
+ --role "Durable Task Data Contributor" `
+ --scope "/subscriptions/$subscriptionId/resourceGroups/my-resource-group/providers/Microsoft.DurableTask/namespaces/my-namespace/taskHubs/portable-dotnet"
+ ```
+
+ Note that it may take a minute for the role assignment to take effect.
+
+1. Get the endpoint for the scheduler resource and save it to the `DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS` environment variable:
+
+ ```powershell
+ $endpoint = az durabletask namespace show `
+ -g my-resource-group `
+ -n my-namespace `
+ --query "properties.url" `
+ -o tsv
+ $env:DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS = $endpoint
+ ```
+
+ The `DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS` environment variable is used by the sample app to connect to the Durable Task Scheduler resource.
+
+1. Save the task hub name to the `DURABLE_TASK_SCHEDULER_TASK_HUB_NAME` environment variable:
+
+ ```powershell
+ $env:DURABLE_TASK_SCHEDULER_TASK_HUB_NAME = "portable-dotnet"
+ ```
+
+ The `DURABLE_TASK_SCHEDULER_TASK_HUB_NAME` environment variable is to configure the sample app with the correct task hub resource name.
+
+## Running the sample
+
+In the same terminal window as above, use the following steps to run the sample on your local machine.
+
+1. Clone this repository.
+
+1. Open a terminal window and navigate to the `samples/portable-sdk/dotnet/AspNetWebApp` directory.
+
+1. Run the following command to build and run the sample:
+
+ ```bash
+ dotnet run
+ ```
+
+You should see output similar to the following:
+
+```plaintext
+Building...
+info: Microsoft.DurableTask[1]
+ Durable Task gRPC worker starting.
+info: Microsoft.Hosting.Lifetime[14]
+ Now listening on: http://localhost:5008
+info: Microsoft.Hosting.Lifetime[0]
+ Application started. Press Ctrl+C to shut down.
+info: Microsoft.Hosting.Lifetime[0]
+ Hosting environment: Development
+info: Microsoft.Hosting.Lifetime[0]
+ Content root path: D:\projects\Azure-Functions-Durable-Task-Scheduler-Private-Preview\samples\portable-sdk\dotnet\AspNetWebApp
+info: Microsoft.DurableTask[4]
+ Sidecar work-item streaming connection established.
+```
+
+## View orchestrations in the dashboard
+
+You can view the orchestrations in the Durable Task Scheduler dashboard by navigating to the namespace-specific dashboard URL in your browser.
+
+Use the following PowerShell command to get the dashboard URL:
+
+```powershell
+$baseUrl = az durabletask namespace show `
+ -g my-resource-group `
+ -n my-namespace `
+ --query "properties.dashboardUrl" `
+ -o tsv
+$dashboardUrl = "$baseUrl/taskHubs/portable-dotnet"
+$dashboardUrl
+```
+
+The URL should look something like the following:
+
+```plaintext
+https://my-namespace-atdngmgxfsh0-db.northcentralus.durabletask.io/taskHubs/portable-dotnet
+```
+
+Once logged in, you should see the orchestrations that were created by the sample app. Below is an example of what the dashboard might look like (note that some of the details will be different than the screenshot):
+
+![Durable Task Scheduler dashboard](/media/images/dtfx-sample-dashboard.png)
+
+
+## Optional: Deploy to Azure Container Apps
+1. Create an container app following the instructions in the [Azure Container App documentation](https://learn.microsoft.com/azure/container-apps/get-started?tabs=bash).
+2. During step 1, specify the deployed container app code folder at samples\portable-sdk\dotnet\AspNetWebApp
+3. Follow the instructions to create a user managed identity and assign the `Durable Task Data Contributor` role then attach it to the container app you created in step 1 at [Azure-Functions-Durable-Task-Scheduler-Private-Preview](..\..\..\..\docs\configure-existing-app.md#run-the-app-on-azure-net). Please skip section "Add required environment variables to app" since these environment variables are not required for deploying to container app.
+4. Call the container app endpoint at `http://sampleapi-.azurecontainerapps.io/api/orchestrators/HelloCities`, Sample curl command:
+
+ ```bash
+ curl -X POST "https://sampleapi-.azurecontainerapps.io/api/orchestrators/HelloCities"
+ ```
+5. You should see the orchestration created in the Durable Task Scheduler dashboard.
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/ScenariosController.cs b/samples/portable-sdk/dotnet/AspNetWebApp/ScenariosController.cs
new file mode 100644
index 00000000..d6049129
--- /dev/null
+++ b/samples/portable-sdk/dotnet/AspNetWebApp/ScenariosController.cs
@@ -0,0 +1,50 @@
+using System.Diagnostics;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Client;
+
+namespace AspNetWebApp;
+
+[Route("scenarios")]
+[ApiController]
+public partial class ScenariosController(
+ DurableTaskClient durableTaskClient,
+ ILogger logger) : ControllerBase
+{
+ readonly DurableTaskClient durableTaskClient = durableTaskClient;
+ readonly ILogger logger = logger;
+
+ [HttpPost("hellocities")]
+ public async Task RunHelloCities([FromQuery] int? count, [FromQuery] string? prefix)
+ {
+ if (count is null || count < 1)
+ {
+ return this.BadRequest(new { error = "A 'count' query string parameter is required and it must contain a positive number." });
+ }
+
+ // Generate a semi-unique prefix for the instance IDs to simplify tracking
+ prefix ??= $"hellocities-{count}-";
+ prefix += DateTime.UtcNow.ToString("yyyyMMdd-hhmmss");
+
+ this.logger.LogInformation("Scheduling {count} orchestrations with a prefix of '{prefix}'...", count, prefix);
+
+ Stopwatch sw = Stopwatch.StartNew();
+ await Enumerable.Range(0, count.Value).ParallelForEachAsync(1000, i =>
+ {
+ string instanceId = $"{prefix}-{i:X16}";
+ return this.durableTaskClient.ScheduleNewHelloCitiesInstanceAsync(
+ input: null!,
+ new StartOrchestrationOptions(instanceId));
+ });
+
+ sw.Stop();
+ this.logger.LogInformation(
+ "All {count} orchestrations were scheduled successfully in {time}ms!",
+ count,
+ sw.ElapsedMilliseconds);
+ return this.Ok(new
+ {
+ message = $"Scheduled {count} orchestrations prefixed with '{prefix}' in {sw.ElapsedMilliseconds}."
+ });
+ }
+}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/Utils.cs b/samples/portable-sdk/dotnet/AspNetWebApp/Utils.cs
new file mode 100644
index 00000000..6ddabf39
--- /dev/null
+++ b/samples/portable-sdk/dotnet/AspNetWebApp/Utils.cs
@@ -0,0 +1,38 @@
+namespace AspNetWebApp;
+
+static class Utils
+{
+ public static async Task ParallelForEachAsync(this IEnumerable items, int maxConcurrency, Func action)
+ {
+ List tasks;
+ if (items is ICollection itemCollection)
+ {
+ tasks = new List(itemCollection.Count);
+ }
+ else
+ {
+ tasks = [];
+ }
+
+ using SemaphoreSlim semaphore = new(maxConcurrency);
+ foreach (T item in items)
+ {
+ tasks.Add(InvokeThrottledAction(item, action, semaphore));
+ }
+
+ await Task.WhenAll(tasks);
+ }
+
+ static async Task InvokeThrottledAction(T item, Func action, SemaphoreSlim semaphore)
+ {
+ await semaphore.WaitAsync();
+ try
+ {
+ await action(item);
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }
+}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Development.json b/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Development.json
new file mode 100644
index 00000000..a6e86ace
--- /dev/null
+++ b/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Production.json b/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Production.json
new file mode 100644
index 00000000..70b7d1d9
--- /dev/null
+++ b/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Production.json
@@ -0,0 +1,11 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS": "https://{your-durable-task-endpoint}.durabletask.io",
+ "DURABLE_TASK_SCHEDULER_TASK_HUB_NAME": "{your-task-hub-name}",
+ "CONTAINER_APP_UMI_CLIENT_ID": "{your-user-managed-identity-client-id}"
+}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.json b/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.json
new file mode 100644
index 00000000..10f68b8c
--- /dev/null
+++ b/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
From 5696b2f8116752f86c53ab6f4ef8a655f4111e5c Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 20:43:26 -0800
Subject: [PATCH 25/58] save
---
.../Azure/DurableTaskSchedulerExtensions.cs | 28 +++++++++++++------
1 file changed, 20 insertions(+), 8 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index ed823d29..c7102bfb 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -27,10 +27,12 @@ public static void UseDurableTaskScheduler(
TokenCredential credential,
Action? configure = null)
{
- DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
-
- configure?.Invoke(options);
+ if (configure is not null)
+ {
+ builder.Services.Configure("DurableTaskSchedulerOptionsForWorker", configure);
+ }
+ DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
builder.UseGrpc(options.GetGrpcChannel());
}
@@ -45,8 +47,12 @@ public static void UseDurableTaskScheduler(
string connectionString,
Action? configure = null)
{
+ if (configure is not null)
+ {
+ builder.Services.Configure(builder.Name, configure);
+ }
+
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
- configure?.Invoke(options);
builder.UseGrpc(options.GetGrpcChannel());
}
@@ -65,10 +71,12 @@ public static void UseDurableTaskScheduler(
TokenCredential credential,
Action? configure = null)
{
- DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
-
- configure?.Invoke(options);
+ if (configure is not null)
+ {
+ builder.Services.Configure(builder.Name, configure);
+ }
+ DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
builder.UseGrpc(options.GetGrpcChannel());
}
@@ -83,8 +91,12 @@ public static void UseDurableTaskScheduler(
string connectionString,
Action? configure = null)
{
+ if (configure is not null)
+ {
+ builder.Services.Configure(builder.Name, configure);
+ }
+
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
- configure?.Invoke(options);
builder.UseGrpc(options.GetGrpcChannel());
}
}
\ No newline at end of file
From 62e2b3043c3d1c49f9494ad6d672bbeb740ded01 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 21:02:02 -0800
Subject: [PATCH 26/58] save
---
.../Azure/DurableTaskSchedulerExtensions.cs | 44 +++++++------------
1 file changed, 16 insertions(+), 28 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index c7102bfb..8e58071d 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -4,6 +4,8 @@
using Azure.Core;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Worker;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
namespace Microsoft.DurableTask.Extensions.Azure;
@@ -12,6 +14,16 @@ namespace Microsoft.DurableTask.Extensions.Azure;
///
public static class DurableTaskSchedulerExtensions
{
+ ///
+ /// Configures DurableTaskScheduler options.
+ ///
+ public static void ConfigureDurableTaskSchedulerOptions(
+ IServiceCollection services,
+ Action configure)
+ {
+ services.Configure(Options.DefaultName, configure);
+ }
+
///
/// Configures Durable Task worker to use the Azure Durable Task Scheduler service.
///
@@ -24,14 +36,8 @@ public static void UseDurableTaskScheduler(
this IDurableTaskWorkerBuilder builder,
string endpointAddress,
string taskHubName,
- TokenCredential credential,
- Action? configure = null)
+ TokenCredential credential)
{
- if (configure is not null)
- {
- builder.Services.Configure("DurableTaskSchedulerOptionsForWorker", configure);
- }
-
DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
builder.UseGrpc(options.GetGrpcChannel());
}
@@ -44,14 +50,8 @@ public static void UseDurableTaskScheduler(
/// Optional callback to configure additional options.
public static void UseDurableTaskScheduler(
this IDurableTaskWorkerBuilder builder,
- string connectionString,
- Action? configure = null)
+ string connectionString)
{
- if (configure is not null)
- {
- builder.Services.Configure(builder.Name, configure);
- }
-
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
builder.UseGrpc(options.GetGrpcChannel());
}
@@ -68,14 +68,8 @@ public static void UseDurableTaskScheduler(
this IDurableTaskClientBuilder builder,
string endpointAddress,
string taskHubName,
- TokenCredential credential,
- Action? configure = null)
+ TokenCredential credential)
{
- if (configure is not null)
- {
- builder.Services.Configure(builder.Name, configure);
- }
-
DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
builder.UseGrpc(options.GetGrpcChannel());
}
@@ -88,14 +82,8 @@ public static void UseDurableTaskScheduler(
/// Optional callback to configure additional options.
public static void UseDurableTaskScheduler(
this IDurableTaskClientBuilder builder,
- string connectionString,
- Action? configure = null)
+ string connectionString)
{
- if (configure is not null)
- {
- builder.Services.Configure(builder.Name, configure);
- }
-
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
builder.UseGrpc(options.GetGrpcChannel());
}
From 02b02c733253878d06229357a78feef33fbafee5 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 23:23:41 -0800
Subject: [PATCH 27/58] update
---
src/Extensions/Azure/Azure.csproj | 1 +
.../Azure/DurableTaskSchedulerExtensions.cs | 107 +++++++++----
.../Azure/DurableTaskSchedulerOptions.cs | 148 ++++++------------
3 files changed, 121 insertions(+), 135 deletions(-)
diff --git a/src/Extensions/Azure/Azure.csproj b/src/Extensions/Azure/Azure.csproj
index 25704be0..d82dd02f 100644
--- a/src/Extensions/Azure/Azure.csproj
+++ b/src/Extensions/Azure/Azure.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 8e58071d..2e8f2e22 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -7,6 +7,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
+
namespace Microsoft.DurableTask.Extensions.Azure;
///
@@ -14,77 +15,115 @@ namespace Microsoft.DurableTask.Extensions.Azure;
///
public static class DurableTaskSchedulerExtensions
{
- ///
- /// Configures DurableTaskScheduler options.
- ///
- public static void ConfigureDurableTaskSchedulerOptions(
- IServiceCollection services,
- Action configure)
- {
- services.Configure(Options.DefaultName, configure);
- }
-
///
/// Configures Durable Task worker to use the Azure Durable Task Scheduler service.
///
- /// The worker builder to configure.
- /// The endpoint address of the Durable Task Scheduler service.
- /// The name of the task hub to connect to.
- /// The credential to use for authentication.
- /// Optional callback to configure additional options.
public static void UseDurableTaskScheduler(
this IDurableTaskWorkerBuilder builder,
string endpointAddress,
string taskHubName,
- TokenCredential credential)
+ TokenCredential credential,
+ Action? configure = null)
{
- DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
+ builder.Services.AddOptions(builder.Name)
+ .Configure(options =>
+ {
+ options.EndpointAddress = endpointAddress;
+ options.TaskHubName = taskHubName;
+ options.Credential = credential;
+ })
+ .Configure(configure ?? (_ => { }))
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+
+ var options = builder.Services.BuildServiceProvider()
+ .GetRequiredService>()
+ .Get(builder.Name);
+
builder.UseGrpc(options.GetGrpcChannel());
}
///
/// Configures Durable Task worker to use the Azure Durable Task Scheduler service using a connection string.
///
- /// The worker builder to configure.
- /// The connection string for the Durable Task Scheduler service.
- /// Optional callback to configure additional options.
public static void UseDurableTaskScheduler(
this IDurableTaskWorkerBuilder builder,
- string connectionString)
+ string connectionString,
+ Action? configure = null)
{
- var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var connectionOptions = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+
+ builder.Services.AddOptions(builder.Name)
+ .Configure(options =>
+ {
+ options.EndpointAddress = connectionOptions.EndpointAddress;
+ options.TaskHubName = connectionOptions.TaskHubName;
+ options.Credential = connectionOptions.Credential;
+ })
+ .Configure(configure ?? (_ => { }))
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+
+ var options = builder.Services.BuildServiceProvider()
+ .GetRequiredService>()
+ .Get(builder.Name);
+
builder.UseGrpc(options.GetGrpcChannel());
}
///
/// Configures Durable Task client to use the Azure Durable Task Scheduler service.
///
- /// The client builder to configure.
- /// The endpoint address of the Durable Task Scheduler service.
- /// The name of the task hub to connect to.
- /// The credential to use for authentication.
- /// Optional callback to configure additional options.
public static void UseDurableTaskScheduler(
this IDurableTaskClientBuilder builder,
string endpointAddress,
string taskHubName,
- TokenCredential credential)
+ TokenCredential credential,
+ Action? configure = null)
{
- DurableTaskSchedulerOptions options = new(endpointAddress, taskHubName, credential);
+ builder.Services.AddOptions(Options.DefaultName)
+ .Configure(options =>
+ {
+ options.EndpointAddress = endpointAddress;
+ options.TaskHubName = taskHubName;
+ options.Credential = credential;
+ })
+ .Configure(configure ?? (_ => { }))
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+
+ var options = builder.Services.BuildServiceProvider()
+ .GetRequiredService>()
+ .Get(Options.DefaultName);
+
builder.UseGrpc(options.GetGrpcChannel());
}
///
/// Configures Durable Task client to use the Azure Durable Task Scheduler service using a connection string.
///
- /// The client builder to configure.
- /// The connection string for the Durable Task Scheduler service.
- /// Optional callback to configure additional options.
public static void UseDurableTaskScheduler(
this IDurableTaskClientBuilder builder,
- string connectionString)
+ string connectionString,
+ Action? configure = null)
{
- var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var connectionOptions = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+
+ builder.Services.AddOptions(Options.DefaultName)
+ .Configure(options =>
+ {
+ options.EndpointAddress = connectionOptions.EndpointAddress;
+ options.TaskHubName = connectionOptions.TaskHubName;
+ options.Credential = connectionOptions.Credential;
+ })
+ .Configure(configure ?? (_ => { }))
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+
+ var options = builder.Services.BuildServiceProvider()
+ .GetRequiredService>()
+ .Get(Options.DefaultName);
+
builder.UseGrpc(options.GetGrpcChannel());
}
}
\ No newline at end of file
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index 3a0c6abd..06fdbf5d 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Azure.Core;
using Azure.Identity;
@@ -13,63 +14,45 @@ namespace Microsoft.DurableTask.Extensions.Azure;
///
public class DurableTaskSchedulerOptions
{
- private readonly string defaultWorkerId;
-
- ///
- /// Initializes a new instance of the class.
- ///
- internal DurableTaskSchedulerOptions(string endpointAddress, string taskHubName, TokenCredential? credential = null)
- {
- Check.NotNullOrEmpty(endpointAddress, nameof(endpointAddress));
- Check.NotNullOrEmpty(taskHubName, nameof(taskHubName));
-
- // Add https:// prefix if no protocol is specified
- this.EndpointAddress = !endpointAddress.Contains("://")
- ? $"https://{endpointAddress}"
- : endpointAddress;
-
- this.TaskHubName = taskHubName;
- this.Credential = credential;
-
- // Generate the default worker ID once at construction time
- // TODO: More iteration needed over time https://github.com/microsoft/durabletask-dotnet/pull/362#discussion_r1909547102
- this.defaultWorkerId = $"{Environment.MachineName},{Environment.ProcessId},{Guid.NewGuid():N}";
- }
-
///
- /// Gets the endpoint address of the Durable Task Scheduler resource.
+ /// Gets or sets the endpoint address of the Durable Task Scheduler resource.
/// Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".
///
- public string EndpointAddress { get; }
+ [Required(ErrorMessage = "Endpoint address is required")]
+ public string EndpointAddress { get; set; } = string.Empty;
///
- /// Gets the name of the task hub resource associated with the Durable Task Scheduler resource.
+ /// Gets or sets the name of the task hub resource associated with the Durable Task Scheduler resource.
///
- public string TaskHubName { get; }
+ [Required(ErrorMessage = "Task hub name is required")]
+ public string TaskHubName { get; set; } = string.Empty;
///
- /// Gets the credential used to authenticate with the Durable Task Scheduler task hub resource.
+ /// Gets or sets the credential used to authenticate with the Durable Task Scheduler task hub resource.
///
- public TokenCredential? Credential { get; }
+ public TokenCredential? Credential { get; set; }
///
/// Gets or sets the resource ID of the Durable Task Scheduler resource.
/// The default value is https://durabletask.io.
///
- public string? ResourceId { get; set; }
+ public string ResourceId { get; set; } = "https://durabletask.io";
///
/// Gets or sets the worker ID used to identify the worker instance.
/// The default value is a string containing the machine name, process ID, and a unique identifier.
///
- public string? WorkerId { get; set; }
+ public string WorkerId { get; set; } = $"{Environment.MachineName},{Environment.ProcessId},{Guid.NewGuid():N}";
+
+ ///
+ /// Gets or sets a value indicating whether to allow insecure channel credentials.
+ /// This should only be set to true in development/testing scenarios.
+ ///
+ public bool AllowInsecureCredentials { get; set; }
///
/// Creates a new instance of from a connection string.
///
- /// The connection string containing the configuration settings.
- /// A new instance of configured with the connection string settings.
- /// Thrown when the connection string contains an unsupported authentication type.
public static DurableTaskSchedulerOptions FromConnectionString(string connectionString)
{
return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
@@ -81,16 +64,15 @@ internal GrpcChannel GetGrpcChannel()
Check.NotNullOrEmpty(this.TaskHubName, nameof(this.TaskHubName));
string taskHubName = this.TaskHubName;
- string endpoint = this.EndpointAddress;
-
- string resourceId = this.ResourceId ?? "https://durabletask.io";
- string workerId = this.WorkerId ?? this.defaultWorkerId;
+ string endpoint = !this.EndpointAddress.Contains("://")
+ ? $"https://{this.EndpointAddress}"
+ : this.EndpointAddress;
AccessTokenCache? cache =
this.Credential is not null
? new AccessTokenCache(
this.Credential,
- new TokenRequestContext(new[] { $"{resourceId}/.default" }),
+ new TokenRequestContext(new[] { $"{this.ResourceId}/.default" }),
TimeSpan.FromMinutes(5))
: null;
@@ -98,90 +80,68 @@ this.Credential is not null
async (context, metadata) =>
{
metadata.Add("taskhub", taskHubName);
- metadata.Add("workerid", workerId);
+ metadata.Add("workerid", this.WorkerId);
if (cache == null)
{
return;
}
-
+
AccessToken token = await cache.GetTokenAsync(context.CancellationToken);
metadata.Add("Authorization", $"Bearer {token.Token}");
});
// Production will use HTTPS, but local testing will use HTTP
- ChannelCredentials channelCreds = endpoint.StartsWith("https://") ?
+ ChannelCredentials channelCreds = endpoint.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ?
ChannelCredentials.SecureSsl :
ChannelCredentials.Insecure;
- return GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
- {
- // The same credential is being used for all operations.
- // https://learn.microsoft.com/aspnet/core/grpc/authn-and-authz#set-the-bearer-token-with-callcredentials
- Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
- // TODO: This is not appropriate for use in production settings. Setting this to true should
- // only be done for local testing. We should hide this setting behind some kind of flag.
- UnsafeUseInsecureChannelCallCredentials = true,
- });
+ return GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
+ {
+ Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
+ UnsafeUseInsecureChannelCallCredentials = this.AllowInsecureCredentials,
+ });
}
///
/// Creates a new instance of from a parsed connection string.
///
- /// The parsed connection string containing the configuration settings.
- /// A new instance of configured with the connection string settings.
- /// Thrown when the connection string contains an unsupported authentication type.
public static DurableTaskSchedulerOptions FromConnectionString(
DurableTaskSchedulerConnectionString connectionString)
{
- // Example connection strings:
- // "Endpoint=https://myaccount.westus3.durabletask.io/;Authentication=ManagedIdentity;ClientID=00000000-0000-0000-0000-000000000000;TaskHubName=th01"
- // "Endpoint=https://myaccount.westus3.durabletask.io/;Authentication=DefaultAzure;TaskHubName=th01"
- // "Endpoint=https://myaccount.westus3.durabletask.io/;Authentication=None;TaskHubName=th01" (undocumented and only intended for local testing)
-
- string endpointAddress = connectionString.Endpoint;
-
- if (!endpointAddress.Contains("://"))
+ var options = new DurableTaskSchedulerOptions
{
- // If the protocol is missing, assume HTTPS.
- endpointAddress = "https://" + endpointAddress;
- }
+ EndpointAddress = connectionString.Endpoint,
+ TaskHubName = connectionString.TaskHubName,
+ Credential = GetCredentialFromConnectionString(connectionString)
+ };
- string authType = connectionString.Authentication;
+ return options;
+ }
- TokenCredential? credential;
+ static TokenCredential? GetCredentialFromConnectionString(DurableTaskSchedulerConnectionString connectionString)
+ {
+ string authType = connectionString.Authentication;
// Parse the supported auth types, in a case-insensitive way and without spaces
switch (authType.ToLower(CultureInfo.InvariantCulture).Replace(" ", string.Empty))
{
case "defaultazure":
- // Default Azure credentials, suitable for a variety of scenarios
- // In many cases, users will need to pass additional configuration options via env vars
- credential = new DefaultAzureCredential();
- break;
+ return new DefaultAzureCredential();
case "managedidentity":
- // Use Managed identity
- // Suitable for Azure-hosted scenarios
- // Note that ClientId could be null for system-assigned managed identities
- credential = new ManagedIdentityCredential(connectionString.ClientId);
- break;
+ return new ManagedIdentityCredential(connectionString.ClientId);
case "workloadidentity":
- // Use Workload Identity Federation.
- // This is commonly-used in Kubernetes (hosted on Azure or anywhere), or in CI environments like
- // Azure Pipelines or GitHub Actions. It can also be used with SPIFFE.
- WorkloadIdentityCredentialOptions opts = new() { };
+ var opts = new WorkloadIdentityCredentialOptions();
if (!string.IsNullOrEmpty(connectionString.ClientId))
{
opts.ClientId = connectionString.ClientId;
}
-
if (!string.IsNullOrEmpty(connectionString.TenantId))
{
opts.TenantId = connectionString.TenantId;
}
-
if (connectionString.AdditionallyAllowedTenants is not null)
{
foreach (string tenant in connectionString.AdditionallyAllowedTenants)
@@ -189,38 +149,24 @@ public static DurableTaskSchedulerOptions FromConnectionString(
opts.AdditionallyAllowedTenants.Add(tenant);
}
}
-
- credential = new WorkloadIdentityCredential(opts);
- break;
+ return new WorkloadIdentityCredential(opts);
case "environment":
- // Use credentials from the environment
- credential = new EnvironmentCredential();
- break;
+ return new EnvironmentCredential();
case "azurecli":
- // Use credentials from the Azure CLI
- credential = new AzureCliCredential();
- break;
+ return new AzureCliCredential();
case "azurepowershell":
- // Use credentials from the Azure PowerShell modules
- credential = new AzurePowerShellCredential();
- break;
+ return new AzurePowerShellCredential();
case "none":
- // Do not use any authentication/authorization (for testing only)
- // This is a no-op
- credential = null;
- break;
+ return null;
default:
throw new ArgumentException(
$"The connection string contains an unsupported authentication type '{authType}'.",
nameof(connectionString));
}
-
- DurableTaskSchedulerOptions options = new(endpointAddress, connectionString.TaskHubName, credential);
- return options;
}
}
\ No newline at end of file
From ee517d2583cdbd7431b7e4d1a413e2320f8a4852 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sat, 11 Jan 2025 08:45:52 -0800
Subject: [PATCH 28/58] save
---
.../Azure/DurableTaskSchedulerExtensions.cs | 48 +++++++++----------
.../Azure/DurableTaskSchedulerOptions.cs | 2 +-
2 files changed, 24 insertions(+), 26 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 2e8f2e22..947caf77 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -36,11 +36,9 @@ public static void UseDurableTaskScheduler(
.ValidateDataAnnotations()
.ValidateOnStart();
- var options = builder.Services.BuildServiceProvider()
- .GetRequiredService>()
- .Get(builder.Name);
-
- builder.UseGrpc(options.GetGrpcChannel());
+ builder.Services.TryAddEnumerable(
+ ServiceDescription.Singleton, ConfigureGrpcChannel>());
+ builder.UseGrpc(_ => { });
}
///
@@ -64,11 +62,9 @@ public static void UseDurableTaskScheduler(
.ValidateDataAnnotations()
.ValidateOnStart();
- var options = builder.Services.BuildServiceProvider()
- .GetRequiredService>()
- .Get(builder.Name);
-
- builder.UseGrpc(options.GetGrpcChannel());
+ builder.Services.TryAddEnumerable(
+ ServiceDescription.Singleton, ConfigureGrpcChannel>());
+ builder.UseGrpc(_ => { });
}
///
@@ -92,11 +88,9 @@ public static void UseDurableTaskScheduler(
.ValidateDataAnnotations()
.ValidateOnStart();
- var options = builder.Services.BuildServiceProvider()
- .GetRequiredService>()
- .Get(Options.DefaultName);
-
- builder.UseGrpc(options.GetGrpcChannel());
+ builder.Services.TryAddEnumerable(
+ ServiceDescription.Singleton, ConfigureGrpcChannel>());
+ builder.UseGrpc(_ => { });
}
///
@@ -110,20 +104,24 @@ public static void UseDurableTaskScheduler(
var connectionOptions = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
builder.Services.AddOptions(Options.DefaultName)
- .Configure(options =>
- {
- options.EndpointAddress = connectionOptions.EndpointAddress;
- options.TaskHubName = connectionOptions.TaskHubName;
- options.Credential = connectionOptions.Credential;
- })
.Configure(configure ?? (_ => { }))
.ValidateDataAnnotations()
.ValidateOnStart();
- var options = builder.Services.BuildServiceProvider()
- .GetRequiredService>()
- .Get(Options.DefaultName);
+ builder.Services.TryAddEnumerable(
+ ServiceDescription.Singleton, ConfigureGrpcChannel>());
+ builder.UseGrpc(_ => { });
+ }
+
+ // helper internal class
+ class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) : IConfigureNamedOptions
+ {
+ public void Configure(GrpcDurableTaskWorkerOptions options) => this.Configure(Options.DefaultName, options);
- builder.UseGrpc(options.GetGrpcChannel());
+ public void Configure(string name, GrpcDurableTaskWorkerOptions options)
+ {
+ DurableTaskSchedulerOptions source = schedulerOptions.Get(name);
+ options.Channel = source.CreateChannel();
+ }
}
}
\ No newline at end of file
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
index 06fdbf5d..25f994dd 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
@@ -58,7 +58,7 @@ public static DurableTaskSchedulerOptions FromConnectionString(string connection
return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
}
- internal GrpcChannel GetGrpcChannel()
+ internal GrpcChannel CreateChannel()
{
Check.NotNullOrEmpty(this.EndpointAddress, nameof(this.EndpointAddress));
Check.NotNullOrEmpty(this.TaskHubName, nameof(this.TaskHubName));
From 34a47addc1bc452b191d2bb7bce422c2dc974062 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sat, 11 Jan 2025 09:05:56 -0800
Subject: [PATCH 29/58] save
---
.../Azure/DurableTaskSchedulerExtensions.cs | 47 +++++++++++++++----
1 file changed, 37 insertions(+), 10 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 947caf77..bc1e2374 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -3,11 +3,13 @@
using Azure.Core;
using Microsoft.DurableTask.Client;
+using Microsoft.DurableTask.Client.Grpc;
using Microsoft.DurableTask.Worker;
+using Microsoft.DurableTask.Worker.Grpc;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
-
namespace Microsoft.DurableTask.Extensions.Azure;
///
@@ -18,6 +20,11 @@ public static class DurableTaskSchedulerExtensions
///
/// Configures Durable Task worker to use the Azure Durable Task Scheduler service.
///
+ /// The Durable Task worker builder to configure.
+ /// The endpoint address of the Durable Task Scheduler resource. Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".
+ /// The name of the task hub resource associated with the Durable Task Scheduler resource.
+ /// The credential used to authenticate with the Durable Task Scheduler task hub resource.
+ /// Optional callback to dynamically configure DurableTaskSchedulerOptions.
public static void UseDurableTaskScheduler(
this IDurableTaskWorkerBuilder builder,
string endpointAddress,
@@ -37,13 +44,16 @@ public static void UseDurableTaskScheduler(
.ValidateOnStart();
builder.Services.TryAddEnumerable(
- ServiceDescription.Singleton, ConfigureGrpcChannel>());
+ ServiceDescriptor.Singleton, ConfigureGrpcChannel>());
builder.UseGrpc(_ => { });
}
///
/// Configures Durable Task worker to use the Azure Durable Task Scheduler service using a connection string.
///
+ /// The Durable Task worker builder to configure.
+ /// The connection string used to connect to the Durable Task Scheduler service.
+ /// Optional callback to dynamically configure DurableTaskSchedulerOptions.
public static void UseDurableTaskScheduler(
this IDurableTaskWorkerBuilder builder,
string connectionString,
@@ -63,13 +73,18 @@ public static void UseDurableTaskScheduler(
.ValidateOnStart();
builder.Services.TryAddEnumerable(
- ServiceDescription.Singleton, ConfigureGrpcChannel>());
+ ServiceDescriptor.Singleton, ConfigureGrpcChannel>());
builder.UseGrpc(_ => { });
}
///
/// Configures Durable Task client to use the Azure Durable Task Scheduler service.
///
+ /// The Durable Task client builder to configure.
+ /// The endpoint address of the Durable Task Scheduler resource. Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".
+ /// The name of the task hub resource associated with the Durable Task Scheduler resource.
+ /// The credential used to authenticate with the Durable Task Scheduler task hub resource.
+ /// Optional callback to dynamically configure DurableTaskSchedulerOptions.
public static void UseDurableTaskScheduler(
this IDurableTaskClientBuilder builder,
string endpointAddress,
@@ -89,13 +104,16 @@ public static void UseDurableTaskScheduler(
.ValidateOnStart();
builder.Services.TryAddEnumerable(
- ServiceDescription.Singleton, ConfigureGrpcChannel>());
+ ServiceDescriptor.Singleton, ConfigureGrpcChannel>());
builder.UseGrpc(_ => { });
}
///
/// Configures Durable Task client to use the Azure Durable Task Scheduler service using a connection string.
///
+ /// The Durable Task client builder to configure.
+ /// The connection string used to connect to the Durable Task Scheduler service.
+ /// Optional callback to dynamically configure DurableTaskSchedulerOptions.
public static void UseDurableTaskScheduler(
this IDurableTaskClientBuilder builder,
string connectionString,
@@ -109,19 +127,28 @@ public static void UseDurableTaskScheduler(
.ValidateOnStart();
builder.Services.TryAddEnumerable(
- ServiceDescription.Singleton, ConfigureGrpcChannel>());
+ ServiceDescriptor.Singleton, ConfigureGrpcChannel>());
builder.UseGrpc(_ => { });
}
- // helper internal class
- class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) : IConfigureNamedOptions
+ class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
+ IConfigureNamedOptions,
+ IConfigureNamedOptions
{
public void Configure(GrpcDurableTaskWorkerOptions options) => this.Configure(Options.DefaultName, options);
- public void Configure(string name, GrpcDurableTaskWorkerOptions options)
+ public void Configure(GrpcDurableTaskClientOptions options) => this.Configure(Options.DefaultName, options);
+
+ public void Configure(string? name, GrpcDurableTaskWorkerOptions options)
+ {
+ DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
+ options.Channel = source.CreateChannel();
+ }
+
+ public void Configure(string? name, GrpcDurableTaskClientOptions options)
{
- DurableTaskSchedulerOptions source = schedulerOptions.Get(name);
+ DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
options.Channel = source.CreateChannel();
}
}
-}
\ No newline at end of file
+}
From 2decb7940e4ad80164cf33f4d5c0d00fd615410a Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sat, 11 Jan 2025 09:07:23 -0800
Subject: [PATCH 30/58] save
---
src/Extensions/Azure/DurableTaskSchedulerExtensions.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index bc1e2374..0be711d4 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -131,7 +131,7 @@ public static void UseDurableTaskScheduler(
builder.UseGrpc(_ => { });
}
- class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
+ internal class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
IConfigureNamedOptions,
IConfigureNamedOptions
{
From ee295d0a0119d5acfc63e15391e4e14d884e453e Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sat, 11 Jan 2025 09:14:32 -0800
Subject: [PATCH 31/58] save
---
.../Azure/DurableTaskSchedulerExtensions.cs | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 0be711d4..aabc23e1 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -131,20 +131,43 @@ public static void UseDurableTaskScheduler(
builder.UseGrpc(_ => { });
}
+ ///
+ /// Internal configuration class that sets up gRPC channels for both worker and client options
+ /// using the provided Durable Task Scheduler options.
+ ///
+ /// Monitor for accessing the current scheduler options configuration.
internal class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
IConfigureNamedOptions,
IConfigureNamedOptions
{
+ ///
+ /// Configures worker options using the default options name.
+ ///
+ /// The worker options to configure.
public void Configure(GrpcDurableTaskWorkerOptions options) => this.Configure(Options.DefaultName, options);
+ ///
+ /// Configures client options using the default options name.
+ ///
+ /// The client options to configure.
public void Configure(GrpcDurableTaskClientOptions options) => this.Configure(Options.DefaultName, options);
+ ///
+ /// Configures named worker options by creating and assigning a gRPC channel.
+ ///
+ /// The name of the options instance being configured, or null for the default instance.
+ /// The worker options to configure.
public void Configure(string? name, GrpcDurableTaskWorkerOptions options)
{
DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
options.Channel = source.CreateChannel();
}
+ ///
+ /// Configures named client options by creating and assigning a gRPC channel.
+ ///
+ /// The name of the options instance being configured, or null for the default instance.
+ /// The client options to configure.
public void Configure(string? name, GrpcDurableTaskClientOptions options)
{
DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
From 522d4b01c45667ba6513ff4398d0a86b4f65bcd5 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sat, 11 Jan 2025 09:38:19 -0800
Subject: [PATCH 32/58] fix
---
.../Azure/DurableTaskSchedulerExtensions.cs | 22 ++-----------------
1 file changed, 2 insertions(+), 20 deletions(-)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index aabc23e1..2be74649 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -92,7 +92,7 @@ public static void UseDurableTaskScheduler(
TokenCredential credential,
Action? configure = null)
{
- builder.Services.AddOptions(Options.DefaultName)
+ builder.Services.AddOptions(builder.Name)
.Configure(options =>
{
options.EndpointAddress = endpointAddress;
@@ -121,7 +121,7 @@ public static void UseDurableTaskScheduler(
{
var connectionOptions = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
- builder.Services.AddOptions(Options.DefaultName)
+ builder.Services.AddOptions(builder.Name)
.Configure(configure ?? (_ => { }))
.ValidateDataAnnotations()
.ValidateOnStart();
@@ -140,34 +140,16 @@ internal class ConfigureGrpcChannel(IOptionsMonitor
IConfigureNamedOptions,
IConfigureNamedOptions
{
- ///
- /// Configures worker options using the default options name.
- ///
- /// The worker options to configure.
public void Configure(GrpcDurableTaskWorkerOptions options) => this.Configure(Options.DefaultName, options);
- ///
- /// Configures client options using the default options name.
- ///
- /// The client options to configure.
public void Configure(GrpcDurableTaskClientOptions options) => this.Configure(Options.DefaultName, options);
- ///
- /// Configures named worker options by creating and assigning a gRPC channel.
- ///
- /// The name of the options instance being configured, or null for the default instance.
- /// The worker options to configure.
public void Configure(string? name, GrpcDurableTaskWorkerOptions options)
{
DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
options.Channel = source.CreateChannel();
}
- ///
- /// Configures named client options by creating and assigning a gRPC channel.
- ///
- /// The name of the options instance being configured, or null for the default instance.
- /// The client options to configure.
public void Configure(string? name, GrpcDurableTaskClientOptions options)
{
DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
From d289c10a51176df1110312bf6ee058f87c710da7 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sat, 11 Jan 2025 09:45:38 -0800
Subject: [PATCH 33/58] fix
---
src/Extensions/Azure/DurableTaskSchedulerExtensions.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
index 2be74649..6692a6a3 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
@@ -122,6 +122,12 @@ public static void UseDurableTaskScheduler(
var connectionOptions = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
builder.Services.AddOptions(builder.Name)
+ .Configure(options =>
+ {
+ options.EndpointAddress = connectionOptions.EndpointAddress;
+ options.TaskHubName = connectionOptions.TaskHubName;
+ options.Credential = connectionOptions.Credential;
+ })
.Configure(configure ?? (_ => { }))
.ValidateDataAnnotations()
.ValidateOnStart();
From 0d2a20ca5e546feed38ce2045a8edb2628ba3a39 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sat, 11 Jan 2025 11:04:23 -0800
Subject: [PATCH 34/58] Revert "sample"
This reverts commit 14b94ebdd51119d92110b9547ee221cf3568a565.
---
Microsoft.DurableTask.sln | 7 -
.../dotnet/AspNetWebApp/AspNetWebApp.csproj | 27 ----
.../dotnet/AspNetWebApp/DockerFile | 22 ---
.../Orchestrations/HelloCities.cs | 30 ----
.../dotnet/AspNetWebApp/Program.cs | 55 -------
.../Properties/launchSettings.json | 23 ---
.../dotnet/AspNetWebApp/README.md | 147 ------------------
.../AspNetWebApp/ScenariosController.cs | 50 ------
.../portable-sdk/dotnet/AspNetWebApp/Utils.cs | 38 -----
.../AspNetWebApp/appsettings.Development.json | 8 -
.../AspNetWebApp/appsettings.Production.json | 11 --
.../dotnet/AspNetWebApp/appsettings.json | 9 --
12 files changed, 427 deletions(-)
delete mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/AspNetWebApp.csproj
delete mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/DockerFile
delete mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/Orchestrations/HelloCities.cs
delete mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/Program.cs
delete mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/Properties/launchSettings.json
delete mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/README.md
delete mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/ScenariosController.cs
delete mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/Utils.cs
delete mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Development.json
delete mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Production.json
delete mode 100644 samples/portable-sdk/dotnet/AspNetWebApp/appsettings.json
diff --git a/Microsoft.DurableTask.sln b/Microsoft.DurableTask.sln
index b7758f81..4e5a4451 100644
--- a/Microsoft.DurableTask.sln
+++ b/Microsoft.DurableTask.sln
@@ -77,8 +77,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure", "src\Extensions\Azu
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Tests", "test\Extensions\Azure\Azure.Tests.csproj", "{DBB5DB4E-A1B0-4C86-A233-213789C46929}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetWebApp", "samples\portable-sdk\dotnet\AspNetWebApp\AspNetWebApp.csproj", "{869D2D51-9372-4764-B059-C43B6C1180A3}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -201,10 +199,6 @@ Global
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Release|Any CPU.Build.0 = Release|Any CPU
- {869D2D51-9372-4764-B059-C43B6C1180A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {869D2D51-9372-4764-B059-C43B6C1180A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {869D2D51-9372-4764-B059-C43B6C1180A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {869D2D51-9372-4764-B059-C43B6C1180A3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -243,7 +237,6 @@ Global
{5227C712-2355-403F-90D6-51D0BCAE4D38} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC} = {5227C712-2355-403F-90D6-51D0BCAE4D38}
{DBB5DB4E-A1B0-4C86-A233-213789C46929} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
- {869D2D51-9372-4764-B059-C43B6C1180A3} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/AspNetWebApp.csproj b/samples/portable-sdk/dotnet/AspNetWebApp/AspNetWebApp.csproj
deleted file mode 100644
index 14516742..00000000
--- a/samples/portable-sdk/dotnet/AspNetWebApp/AspNetWebApp.csproj
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
- net8.0
- enable
- enable
- true
- $(BaseIntermediateOutputPath)Generated
-
-
- false
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/DockerFile b/samples/portable-sdk/dotnet/AspNetWebApp/DockerFile
deleted file mode 100644
index 5ddb3ae9..00000000
--- a/samples/portable-sdk/dotnet/AspNetWebApp/DockerFile
+++ /dev/null
@@ -1,22 +0,0 @@
-#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
-
-FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
-WORKDIR /app
-EXPOSE 8080
-
-FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
-WORKDIR /src
-COPY ["AspNetWebApp.csproj", "."]
-RUN dotnet restore "./AspNetWebApp.csproj"
-COPY . .
-WORKDIR "/src/."
-RUN dotnet build "AspNetWebApp.csproj" -c Release -o /app/build
-
-FROM build AS publish
-RUN dotnet publish "AspNetWebApp.csproj" -c Release -o /app/publish /p:UseAppHost=false
-
-FROM base AS final
-WORKDIR /app
-COPY --from=publish /app/publish .
-ENV ASPNETCORE_ENVIRONMENT=Production
-ENTRYPOINT ["dotnet", "AspNetWebApp.dll"]
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/Orchestrations/HelloCities.cs b/samples/portable-sdk/dotnet/AspNetWebApp/Orchestrations/HelloCities.cs
deleted file mode 100644
index 5c48dcaa..00000000
--- a/samples/portable-sdk/dotnet/AspNetWebApp/Orchestrations/HelloCities.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Microsoft.DurableTask;
-using Microsoft.DurableTask.Client;
-
-namespace AspNetWebApp.Scenarios;
-
-[DurableTask]
-class HelloCities : TaskOrchestrator>
-{
- public override async Task> RunAsync(TaskOrchestrationContext context, string input)
- {
- List results =
- [
- await context.CallSayHelloAsync("Seattle"),
- await context.CallSayHelloAsync("Amsterdam"),
- await context.CallSayHelloAsync("Hyderabad"),
- await context.CallSayHelloAsync("Shanghai"),
- await context.CallSayHelloAsync("Tokyo"),
- ];
- return results;
- }
-}
-
-[DurableTask]
-class SayHello : TaskActivity
-{
- public override Task RunAsync(TaskActivityContext context, string cityName)
- {
- return Task.FromResult($"Hello, {cityName}!");
- }
-}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/Program.cs b/samples/portable-sdk/dotnet/AspNetWebApp/Program.cs
deleted file mode 100644
index 4c21b7b2..00000000
--- a/samples/portable-sdk/dotnet/AspNetWebApp/Program.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System.Text.Json.Serialization;
-using Azure.Core;
-using Azure.Identity;
-using Microsoft.DurableTask;
-using Microsoft.DurableTask.Client;
-using Microsoft.DurableTask.Worker;
-using Microsoft.DurableTask.Extensions.Azure;
-
-WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
-
-string endpointAddress = builder.Configuration["DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS"]
- ?? throw new InvalidOperationException("Missing required configuration 'DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS'");
-
-string taskHubName = builder.Configuration["DURABLE_TASK_SCHEDULER_TASK_HUB_NAME"]
- ?? throw new InvalidOperationException("Missing required configuration 'DURABLE_TASK_SCHEDULER_TASK_HUB_NAME'");
-
-TokenCredential credential = builder.Environment.IsProduction()
- ? new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = builder.Configuration["CONTAINER_APP_UMI_CLIENT_ID"] })
- : new DefaultAzureCredential();
-
-// Add all the generated orchestrations and activities automatically
-builder.Services.AddDurableTaskWorker(builder =>
-{
- builder.AddTasks(r => r.AddAllGeneratedTasks());
- builder.UseDurableTaskScheduler(endpointAddress, taskHubName, credential);
-});
-
-// Register the client, which can be used to start orchestrations
-builder.Services.AddDurableTaskClient(builder =>
-{
- builder.UseDurableTaskScheduler(endpointAddress, taskHubName, credential);
-});
-
-// Configure console logging using the simpler, more compact format
-builder.Services.AddLogging(logging =>
-{
- logging.AddSimpleConsole(options =>
- {
- options.SingleLine = true;
- options.UseUtcTimestamp = true;
- options.TimestampFormat = "yyyy-MM-ddTHH:mm:ss.fffZ ";
- });
-});
-
-// Configure the HTTP request pipeline
-builder.Services.AddControllers().AddJsonOptions(options =>
-{
- options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
-});
-
-// The actual listen URL can be configured in environment variables named "ASPNETCORE_URLS" or "ASPNETCORE_URLS_HTTPS"
-WebApplication app = builder.Build();
-app.MapControllers();
-app.Run();
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/Properties/launchSettings.json b/samples/portable-sdk/dotnet/AspNetWebApp/Properties/launchSettings.json
deleted file mode 100644
index 4ee2ec75..00000000
--- a/samples/portable-sdk/dotnet/AspNetWebApp/Properties/launchSettings.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "$schema": "http://json.schemastore.org/launchsettings.json",
- "iisSettings": {
- "windowsAuthentication": false,
- "anonymousAuthentication": true,
- "iisExpress": {
- "applicationUrl": "http://localhost:36209",
- "sslPort": 0
- }
- },
- "profiles": {
- "http": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "applicationUrl": "http://localhost:5008",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS": "https://localhost:8082",
- "DURABLE_TASK_SCHEDULER_TASK_HUB_NAME": "samples"
- }
- }
- }
-}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/README.md b/samples/portable-sdk/dotnet/AspNetWebApp/README.md
deleted file mode 100644
index 4875b63e..00000000
--- a/samples/portable-sdk/dotnet/AspNetWebApp/README.md
+++ /dev/null
@@ -1,147 +0,0 @@
-# Hello World with the Durable Task SDK for .NET
-
-In addition to [Durable Functions](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview), the [Durable Task SDK for .NET](https://github.com/microsoft/durabletask-dotnet) can also use the Durable Task Scheduler service for managing orchestration state.
-
-This directory includes a sample .NET console app that demonstrates how to use the Durable Task Scheduler with the Durable Task SDK for .NET (without any Azure Functions dependency).
-
-## Prerequisites
-
-- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
-- [PowerShell](https://docs.microsoft.com/powershell/scripting/install/installing-powershell)
-- [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli)
-
-## Creating a Durable Task Scheduler task hub
-
-Before you can run the app, you need to create a Durable Task Scheduler task hub in Azure and produce a connection string that references it.
-
-> **NOTE**: These are abbreviated instructions for simplicity. For a full set of instructions, see the Azure Durable Functions [QuickStart guide](../../../../quickstarts/HelloCities/README.md#create-a-durable-task-scheduler-namespace-and-task-hub).
-
-1. Install the Durable Task Scheduler CLI extension:
-
- ```bash
- az upgrade
- az extension add --name durabletask --allow-preview true
- ```
-
-1. Create a resource group:
-
- ```powershell
- az group create --name my-resource-group --location northcentralus
- ```
-
-1. Create a Durable Task Scheduler namespace:
-
- ```powershell
- az durabletask namespace create -g my-resource-group --name my-namespace
- ```
-
-1. Create a task hub within the namespace:
-
- ```powershell
- az durabletask taskhub create -g my-resource-group --namespace my-namespace --name "portable-dotnet"
- ```
-
-1. Grant the current user permission to connect to the `portable-dotnet` task hub:
-
- ```powershell
- $subscriptionId = az account show --query "id" -o tsv
- $loggedInUser = az account show --query "user.name" -o tsv
-
- az role assignment create `
- --assignee $loggedInUser `
- --role "Durable Task Data Contributor" `
- --scope "/subscriptions/$subscriptionId/resourceGroups/my-resource-group/providers/Microsoft.DurableTask/namespaces/my-namespace/taskHubs/portable-dotnet"
- ```
-
- Note that it may take a minute for the role assignment to take effect.
-
-1. Get the endpoint for the scheduler resource and save it to the `DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS` environment variable:
-
- ```powershell
- $endpoint = az durabletask namespace show `
- -g my-resource-group `
- -n my-namespace `
- --query "properties.url" `
- -o tsv
- $env:DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS = $endpoint
- ```
-
- The `DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS` environment variable is used by the sample app to connect to the Durable Task Scheduler resource.
-
-1. Save the task hub name to the `DURABLE_TASK_SCHEDULER_TASK_HUB_NAME` environment variable:
-
- ```powershell
- $env:DURABLE_TASK_SCHEDULER_TASK_HUB_NAME = "portable-dotnet"
- ```
-
- The `DURABLE_TASK_SCHEDULER_TASK_HUB_NAME` environment variable is to configure the sample app with the correct task hub resource name.
-
-## Running the sample
-
-In the same terminal window as above, use the following steps to run the sample on your local machine.
-
-1. Clone this repository.
-
-1. Open a terminal window and navigate to the `samples/portable-sdk/dotnet/AspNetWebApp` directory.
-
-1. Run the following command to build and run the sample:
-
- ```bash
- dotnet run
- ```
-
-You should see output similar to the following:
-
-```plaintext
-Building...
-info: Microsoft.DurableTask[1]
- Durable Task gRPC worker starting.
-info: Microsoft.Hosting.Lifetime[14]
- Now listening on: http://localhost:5008
-info: Microsoft.Hosting.Lifetime[0]
- Application started. Press Ctrl+C to shut down.
-info: Microsoft.Hosting.Lifetime[0]
- Hosting environment: Development
-info: Microsoft.Hosting.Lifetime[0]
- Content root path: D:\projects\Azure-Functions-Durable-Task-Scheduler-Private-Preview\samples\portable-sdk\dotnet\AspNetWebApp
-info: Microsoft.DurableTask[4]
- Sidecar work-item streaming connection established.
-```
-
-## View orchestrations in the dashboard
-
-You can view the orchestrations in the Durable Task Scheduler dashboard by navigating to the namespace-specific dashboard URL in your browser.
-
-Use the following PowerShell command to get the dashboard URL:
-
-```powershell
-$baseUrl = az durabletask namespace show `
- -g my-resource-group `
- -n my-namespace `
- --query "properties.dashboardUrl" `
- -o tsv
-$dashboardUrl = "$baseUrl/taskHubs/portable-dotnet"
-$dashboardUrl
-```
-
-The URL should look something like the following:
-
-```plaintext
-https://my-namespace-atdngmgxfsh0-db.northcentralus.durabletask.io/taskHubs/portable-dotnet
-```
-
-Once logged in, you should see the orchestrations that were created by the sample app. Below is an example of what the dashboard might look like (note that some of the details will be different than the screenshot):
-
-![Durable Task Scheduler dashboard](/media/images/dtfx-sample-dashboard.png)
-
-
-## Optional: Deploy to Azure Container Apps
-1. Create an container app following the instructions in the [Azure Container App documentation](https://learn.microsoft.com/azure/container-apps/get-started?tabs=bash).
-2. During step 1, specify the deployed container app code folder at samples\portable-sdk\dotnet\AspNetWebApp
-3. Follow the instructions to create a user managed identity and assign the `Durable Task Data Contributor` role then attach it to the container app you created in step 1 at [Azure-Functions-Durable-Task-Scheduler-Private-Preview](..\..\..\..\docs\configure-existing-app.md#run-the-app-on-azure-net). Please skip section "Add required environment variables to app" since these environment variables are not required for deploying to container app.
-4. Call the container app endpoint at `http://sampleapi-.azurecontainerapps.io/api/orchestrators/HelloCities`, Sample curl command:
-
- ```bash
- curl -X POST "https://sampleapi-.azurecontainerapps.io/api/orchestrators/HelloCities"
- ```
-5. You should see the orchestration created in the Durable Task Scheduler dashboard.
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/ScenariosController.cs b/samples/portable-sdk/dotnet/AspNetWebApp/ScenariosController.cs
deleted file mode 100644
index d6049129..00000000
--- a/samples/portable-sdk/dotnet/AspNetWebApp/ScenariosController.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System.Diagnostics;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.DurableTask;
-using Microsoft.DurableTask.Client;
-
-namespace AspNetWebApp;
-
-[Route("scenarios")]
-[ApiController]
-public partial class ScenariosController(
- DurableTaskClient durableTaskClient,
- ILogger logger) : ControllerBase
-{
- readonly DurableTaskClient durableTaskClient = durableTaskClient;
- readonly ILogger logger = logger;
-
- [HttpPost("hellocities")]
- public async Task RunHelloCities([FromQuery] int? count, [FromQuery] string? prefix)
- {
- if (count is null || count < 1)
- {
- return this.BadRequest(new { error = "A 'count' query string parameter is required and it must contain a positive number." });
- }
-
- // Generate a semi-unique prefix for the instance IDs to simplify tracking
- prefix ??= $"hellocities-{count}-";
- prefix += DateTime.UtcNow.ToString("yyyyMMdd-hhmmss");
-
- this.logger.LogInformation("Scheduling {count} orchestrations with a prefix of '{prefix}'...", count, prefix);
-
- Stopwatch sw = Stopwatch.StartNew();
- await Enumerable.Range(0, count.Value).ParallelForEachAsync(1000, i =>
- {
- string instanceId = $"{prefix}-{i:X16}";
- return this.durableTaskClient.ScheduleNewHelloCitiesInstanceAsync(
- input: null!,
- new StartOrchestrationOptions(instanceId));
- });
-
- sw.Stop();
- this.logger.LogInformation(
- "All {count} orchestrations were scheduled successfully in {time}ms!",
- count,
- sw.ElapsedMilliseconds);
- return this.Ok(new
- {
- message = $"Scheduled {count} orchestrations prefixed with '{prefix}' in {sw.ElapsedMilliseconds}."
- });
- }
-}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/Utils.cs b/samples/portable-sdk/dotnet/AspNetWebApp/Utils.cs
deleted file mode 100644
index 6ddabf39..00000000
--- a/samples/portable-sdk/dotnet/AspNetWebApp/Utils.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-namespace AspNetWebApp;
-
-static class Utils
-{
- public static async Task ParallelForEachAsync(this IEnumerable items, int maxConcurrency, Func action)
- {
- List tasks;
- if (items is ICollection itemCollection)
- {
- tasks = new List(itemCollection.Count);
- }
- else
- {
- tasks = [];
- }
-
- using SemaphoreSlim semaphore = new(maxConcurrency);
- foreach (T item in items)
- {
- tasks.Add(InvokeThrottledAction(item, action, semaphore));
- }
-
- await Task.WhenAll(tasks);
- }
-
- static async Task InvokeThrottledAction(T item, Func action, SemaphoreSlim semaphore)
- {
- await semaphore.WaitAsync();
- try
- {
- await action(item);
- }
- finally
- {
- semaphore.Release();
- }
- }
-}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Development.json b/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Development.json
deleted file mode 100644
index a6e86ace..00000000
--- a/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Development.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Debug",
- "Microsoft.AspNetCore": "Warning"
- }
- }
-}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Production.json b/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Production.json
deleted file mode 100644
index 70b7d1d9..00000000
--- a/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.Production.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
- },
- "DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS": "https://{your-durable-task-endpoint}.durabletask.io",
- "DURABLE_TASK_SCHEDULER_TASK_HUB_NAME": "{your-task-hub-name}",
- "CONTAINER_APP_UMI_CLIENT_ID": "{your-user-managed-identity-client-id}"
-}
diff --git a/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.json b/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.json
deleted file mode 100644
index 10f68b8c..00000000
--- a/samples/portable-sdk/dotnet/AspNetWebApp/appsettings.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
- },
- "AllowedHosts": "*"
-}
From f4f03fcc865a16e4e17fc951eeb744ab8af13cc1 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sat, 11 Jan 2025 11:31:06 -0800
Subject: [PATCH 35/58] fix tests
---
...rableTaskSchedulerConnectionStringTests.cs | 80 +++++++++-
.../DurableTaskSchedulerExtensionsTests.cs | 137 +++++++++++++++++-
.../Azure/DurableTaskSchedulerOptionsTests.cs | 105 +++++++++++++-
3 files changed, 304 insertions(+), 18 deletions(-)
diff --git a/test/Extensions/Azure/DurableTaskSchedulerConnectionStringTests.cs b/test/Extensions/Azure/DurableTaskSchedulerConnectionStringTests.cs
index aaca0a66..75372d6d 100644
--- a/test/Extensions/Azure/DurableTaskSchedulerConnectionStringTests.cs
+++ b/test/Extensions/Azure/DurableTaskSchedulerConnectionStringTests.cs
@@ -71,6 +71,77 @@ public void Constructor_WithAdditionallyAllowedTenants_ShouldParseTenantList()
parsedConnectionString.AdditionallyAllowedTenants.Should().BeEquivalentTo(new[] { "tenant1", "tenant2", "tenant3" });
}
+ [Fact]
+ public void Constructor_WithMultipleAdditionallyAllowedTenants_ShouldParseCorrectly()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=WorkloadIdentity;TaskHub={ValidTaskHub};" +
+ "AdditionallyAllowedTenants=tenant1,tenant2,tenant3";
+
+ // Act
+ var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+
+ // Assert
+ parsedConnectionString.AdditionallyAllowedTenants.Should().NotBeNull();
+ parsedConnectionString.AdditionallyAllowedTenants!.Should().HaveCount(3);
+ parsedConnectionString.AdditionallyAllowedTenants.Should().Contain(new[] { "tenant1", "tenant2", "tenant3" });
+ }
+
+ [Fact]
+ public void Constructor_WithCaseInsensitivePropertyNames_ShouldParseCorrectly()
+ {
+ // Arrange
+ string connectionString = $"endpoint={ValidEndpoint};AUTHENTICATION=DefaultAzure;taskhub={ValidTaskHub};" +
+ $"clientid={ValidClientId};tenantid={ValidTenantId}";
+
+ // Act
+ var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+
+ // Assert
+ parsedConnectionString.Endpoint.Should().Be(ValidEndpoint);
+ parsedConnectionString.Authentication.Should().Be("DefaultAzure");
+ parsedConnectionString.TaskHubName.Should().Be(ValidTaskHub);
+ parsedConnectionString.ClientId.Should().Be(ValidClientId);
+ parsedConnectionString.TenantId.Should().Be(ValidTenantId);
+ }
+
+ [Fact]
+ public void Constructor_WithInvalidConnectionStringFormat_ShouldThrowFormatException()
+ {
+ // Arrange
+ var connectionString = "This is not a valid=connection string format";
+
+ // Act & Assert
+ var action = () => _ = new DurableTaskSchedulerConnectionString(connectionString).Endpoint;
+ action.Should().Throw()
+ .WithMessage("Value cannot be null. (Parameter 'The connection string is missing the required 'Endpoint' property.')");
+ }
+
+ [Fact]
+ public void Constructor_WithEmptyConnectionString_ShouldThrowArgumentNullException()
+ {
+ // Arrange
+ var connectionString = string.Empty;
+
+ // Act & Assert
+ var action = () => _ = new DurableTaskSchedulerConnectionString(connectionString).Endpoint;
+ action.Should().Throw()
+ .WithMessage("Value cannot be null. (Parameter 'The connection string is missing the required 'Endpoint' property.')");
+ }
+
+ [Fact]
+ public void Constructor_WithDuplicateKeys_ShouldUseLastValue()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub=hub1;TaskHub=hub2";
+
+ // Act
+ var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+
+ // Assert
+ parsedConnectionString.TaskHubName.Should().Be("hub2");
+ }
+
[Fact]
public void Constructor_WithMissingRequiredProperties_ShouldThrowArgumentNullException()
{
@@ -79,8 +150,8 @@ public void Constructor_WithMissingRequiredProperties_ShouldThrowArgumentNullExc
// Act & Assert
var action = () => _ = new DurableTaskSchedulerConnectionString(connectionString).Endpoint;
- var exception = action.Should().Throw().Which;
- exception.Message.Should().Contain("'Endpoint' property");
+ action.Should().Throw()
+ .WithMessage("*'Endpoint' property*");
}
[Fact]
@@ -98,12 +169,11 @@ public void Constructor_WithInvalidConnectionString_ShouldThrowArgumentException
[Theory]
[InlineData("")]
[InlineData(null)]
- public void Constructor_WithNullOrEmptyConnectionString_ShouldThrowArgumentException(string? connectionString)
+ public void Constructor_WithNullOrEmptyConnectionString_ShouldThrowArgumentNullException(string? connectionString)
{
// Act & Assert
var action = () => _ = new DurableTaskSchedulerConnectionString(connectionString!).Endpoint;
- action.Should().Throw()
- .WithMessage("*'Endpoint' property*");
+ action.Should().Throw();
}
[Fact]
diff --git a/test/Extensions/Azure/DurableTaskSchedulerExtensionsTests.cs b/test/Extensions/Azure/DurableTaskSchedulerExtensionsTests.cs
index 518b53c1..4bd73bfa 100644
--- a/test/Extensions/Azure/DurableTaskSchedulerExtensionsTests.cs
+++ b/test/Extensions/Azure/DurableTaskSchedulerExtensionsTests.cs
@@ -12,6 +12,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
+using System.ComponentModel.DataAnnotations;
using Xunit;
namespace Microsoft.DurableTask.Extensions.Azure.Tests;
@@ -117,8 +118,8 @@ public void UseDurableTaskScheduler_WithOptions_ShouldApplyConfiguration()
}
[Theory]
- [InlineData(null, ValidTaskHub)]
- [InlineData(ValidEndpoint, null)]
+ [InlineData(null, "testhub")]
+ [InlineData("myaccount.westus3.durabletask.io", null)]
public void UseDurableTaskScheduler_WithNullParameters_ShouldThrowArgumentNullException(string endpoint, string taskHub)
{
// Arrange
@@ -127,9 +128,21 @@ public void UseDurableTaskScheduler_WithNullParameters_ShouldThrowArgumentNullEx
mockBuilder.Setup(b => b.Services).Returns(services);
var credential = new DefaultAzureCredential();
- // Act & Assert
+ // Act
var action = () => mockBuilder.Object.UseDurableTaskScheduler(endpoint, taskHub, credential);
- action.Should().Throw();
+
+ // Assert
+ action.Should().NotThrow(); // The validation happens when building the service provider
+
+ if (endpoint == null || taskHub == null)
+ {
+ var provider = services.BuildServiceProvider();
+ var ex = Assert.Throws(() =>
+ {
+ var options = provider.GetRequiredService>().Value;
+ });
+ Assert.Contains(endpoint == null ? "EndpointAddress" : "TaskHubName", ex.Message);
+ }
}
[Fact]
@@ -153,11 +166,14 @@ public void UseDurableTaskScheduler_WithInvalidConnectionString_ShouldThrowArgum
var services = new ServiceCollection();
var mockBuilder = new Mock();
mockBuilder.Setup(b => b.Services).Returns(services);
- string invalidConnectionString = "This is not a valid connection string";
+ var connectionString = "This is not a valid=connection string format";
- // Act & Assert
- var action = () => mockBuilder.Object.UseDurableTaskScheduler(invalidConnectionString);
- action.Should().Throw();
+ // Act
+ var action = () => mockBuilder.Object.UseDurableTaskScheduler(connectionString);
+
+ // Assert
+ action.Should().Throw()
+ .WithMessage("Value cannot be null. (Parameter 'The connection string is missing the required 'Endpoint' property.')");
}
[Theory]
@@ -174,4 +190,109 @@ public void UseDurableTaskScheduler_WithNullOrEmptyConnectionString_ShouldThrowA
var action = () => mockBuilder.Object.UseDurableTaskScheduler(connectionString);
action.Should().Throw();
}
+
+ [Fact]
+ public void UseDurableTaskScheduler_Worker_WithValidationFailure_ShouldThrowValidationException()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ var credential = new DefaultAzureCredential();
+
+ // Act
+ mockBuilder.Object.UseDurableTaskScheduler(string.Empty, ValidTaskHub, credential);
+
+ // Assert
+ var ex = Assert.Throws(() =>
+ {
+ var provider = services.BuildServiceProvider();
+ var options = provider.GetRequiredService>().Value;
+ });
+
+ Assert.Contains("EndpointAddress", ex.Message);
+ }
+
+ [Fact]
+ public void UseDurableTaskScheduler_Client_WithNamedOptions_ShouldConfigureCorrectly()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ mockBuilder.Setup(b => b.Name).Returns("CustomName");
+ var credential = new DefaultAzureCredential();
+
+ // Act
+ mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential);
+
+ // Assert
+ var provider = services.BuildServiceProvider();
+ var optionsMonitor = provider.GetService>();
+ optionsMonitor.Should().NotBeNull();
+ var options = optionsMonitor!.Get("CustomName");
+ options.Should().NotBeNull();
+ options.EndpointAddress.Should().Be(ValidEndpoint); // The https:// prefix is added by CreateChannel, not in the extension method
+ options.TaskHubName.Should().Be(ValidTaskHub);
+ options.Credential.Should().BeOfType();
+ }
+
+ [Fact]
+ public void ConfigureGrpcChannel_ShouldConfigureWorkerAndClientOptions()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddOptions()
+ .Configure(options =>
+ {
+ options.EndpointAddress = $"https://{ValidEndpoint}";
+ options.TaskHubName = ValidTaskHub;
+ options.Credential = new DefaultAzureCredential();
+ });
+
+ var provider = services.BuildServiceProvider();
+ var schedulerOptions = provider.GetService>();
+ schedulerOptions.Should().NotBeNull("SchedulerOptions should be registered");
+ var configureChannel = new DurableTaskSchedulerExtensions.ConfigureGrpcChannel(schedulerOptions!);
+
+ var workerOptions = new GrpcDurableTaskWorkerOptions();
+ var clientOptions = new GrpcDurableTaskClientOptions();
+
+ // Act
+ configureChannel.Configure(Options.DefaultName, workerOptions);
+ configureChannel.Configure(Options.DefaultName, clientOptions);
+
+ // Assert
+ workerOptions.Channel.Should().NotBeNull();
+ clientOptions.Channel.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void UseDurableTaskScheduler_WithCustomConfiguration_ShouldApplyConfiguration()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ var credential = new DefaultAzureCredential();
+ string customWorkerId = "custom-worker";
+
+ // Act
+ mockBuilder.Object.UseDurableTaskScheduler(
+ ValidEndpoint,
+ ValidTaskHub,
+ credential,
+ options =>
+ {
+ options.WorkerId = customWorkerId;
+ options.AllowInsecureCredentials = true;
+ });
+
+ // Assert
+ var provider = services.BuildServiceProvider();
+ var options = provider.GetService>();
+ options.Should().NotBeNull();
+ options.Value.WorkerId.Should().Be(customWorkerId);
+ options.Value.AllowInsecureCredentials.Should().BeTrue();
+ }
}
diff --git a/test/Extensions/Azure/DurableTaskSchedulerOptionsTests.cs b/test/Extensions/Azure/DurableTaskSchedulerOptionsTests.cs
index b1c903c2..7dc0c591 100644
--- a/test/Extensions/Azure/DurableTaskSchedulerOptionsTests.cs
+++ b/test/Extensions/Azure/DurableTaskSchedulerOptionsTests.cs
@@ -23,7 +23,7 @@ public void FromConnectionString_WithDefaultAzure_ShouldCreateValidInstance()
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
// Assert
- options.EndpointAddress.Should().Be($"https://{ValidEndpoint}");
+ options.EndpointAddress.Should().Be(ValidEndpoint);
options.TaskHubName.Should().Be(ValidTaskHub);
options.Credential.Should().BeOfType();
}
@@ -39,7 +39,7 @@ public void FromConnectionString_WithManagedIdentity_ShouldCreateValidInstance()
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
// Assert
- options.EndpointAddress.Should().Be($"https://{ValidEndpoint}");
+ options.EndpointAddress.Should().Be(ValidEndpoint);
options.TaskHubName.Should().Be(ValidTaskHub);
options.Credential.Should().BeOfType();
}
@@ -56,7 +56,7 @@ public void FromConnectionString_WithWorkloadIdentity_ShouldCreateValidInstance(
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
// Assert
- options.EndpointAddress.Should().Be($"https://{ValidEndpoint}");
+ options.EndpointAddress.Should().Be(ValidEndpoint);
options.TaskHubName.Should().Be(ValidTaskHub);
options.Credential.Should().BeOfType();
}
@@ -74,7 +74,7 @@ public void FromConnectionString_WithValidAuthTypes_ShouldCreateValidInstance(st
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
// Assert
- options.EndpointAddress.Should().Be($"https://{ValidEndpoint}");
+ options.EndpointAddress.Should().Be(ValidEndpoint);
options.TaskHubName.Should().Be(ValidTaskHub);
options.Credential.Should().NotBeNull();
}
@@ -112,8 +112,103 @@ public void FromConnectionString_WithNone_ShouldCreateInstanceWithNullCredential
var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
// Assert
- options.EndpointAddress.Should().Be($"https://{ValidEndpoint}");
+ options.EndpointAddress.Should().Be(ValidEndpoint);
options.TaskHubName.Should().Be(ValidTaskHub);
options.Credential.Should().BeNull();
}
+
+ [Fact]
+ public void DefaultProperties_ShouldHaveExpectedValues()
+ {
+ // Arrange & Act
+ var options = new DurableTaskSchedulerOptions();
+
+ // Assert
+ options.ResourceId.Should().Be("https://durabletask.io");
+ options.WorkerId.Should().NotBeNullOrEmpty();
+ options.WorkerId.Should().Contain(Environment.MachineName);
+ options.WorkerId.Should().Contain(Environment.ProcessId.ToString());
+ options.AllowInsecureCredentials.Should().BeFalse();
+ }
+
+ [Fact]
+ public void CreateChannel_WithHttpsEndpoint_ShouldCreateSecureChannel()
+ {
+ // Arrange
+ var options = new DurableTaskSchedulerOptions
+ {
+ EndpointAddress = $"https://{ValidEndpoint}",
+ TaskHubName = ValidTaskHub,
+ Credential = new DefaultAzureCredential()
+ };
+
+ // Act
+ var channel = options.CreateChannel();
+
+ // Assert
+ channel.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void CreateChannel_WithHttpEndpoint_ShouldCreateInsecureChannel()
+ {
+ // Arrange
+ var options = new DurableTaskSchedulerOptions
+ {
+ EndpointAddress = $"http://{ValidEndpoint}",
+ TaskHubName = ValidTaskHub,
+ AllowInsecureCredentials = true
+ };
+
+ // Act
+ var channel = options.CreateChannel();
+
+ // Assert
+ channel.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void FromConnectionString_WithInvalidEndpoint_ShouldThrowArgumentException()
+ {
+ // Arrange
+ var connectionString = "Endpoint=not a valid endpoint;Authentication=DefaultAzure;TaskHub=testhub;";
+
+ // Act & Assert
+ var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var action = () => options.CreateChannel();
+ action.Should().Throw()
+ .WithMessage("Invalid URI: The hostname could not be parsed.");
+ }
+
+ [Fact]
+ public void FromConnectionString_WithoutProtocol_ShouldPreserveEndpoint()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
+
+ // Act
+ var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+
+ // Assert
+ options.EndpointAddress.Should().Be(ValidEndpoint);
+ }
+
+ [Fact]
+ public void CreateChannel_ShouldAddHttpsPrefix()
+ {
+ // Arrange
+ var options = new DurableTaskSchedulerOptions
+ {
+ EndpointAddress = ValidEndpoint,
+ TaskHubName = ValidTaskHub,
+ Credential = new DefaultAzureCredential()
+ };
+
+ // Act
+ var channel = options.CreateChannel();
+
+ // Assert
+ channel.Should().NotBeNull();
+ // Note: We can't directly test the endpoint in the channel as it's not exposed
+ }
}
From 59c5e9c1e9f4d2a46c7b31d617607a925d02cfc2 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sat, 11 Jan 2025 11:42:04 -0800
Subject: [PATCH 36/58] test accesstokencache
---
.../Extensions/Azure/AccessTokenCacheTests.cs | 120 ++++++++++++++++++
1 file changed, 120 insertions(+)
create mode 100644 test/Extensions/Azure/AccessTokenCacheTests.cs
diff --git a/test/Extensions/Azure/AccessTokenCacheTests.cs b/test/Extensions/Azure/AccessTokenCacheTests.cs
new file mode 100644
index 00000000..8c766010
--- /dev/null
+++ b/test/Extensions/Azure/AccessTokenCacheTests.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure.Core;
+using FluentAssertions;
+using Microsoft.DurableTask.Extensions.Azure;
+using Moq;
+using Xunit;
+
+namespace Microsoft.DurableTask.Tests.Extensions.Azure;
+
+public class AccessTokenCacheTests
+{
+ private readonly Mock mockCredential;
+ private readonly TokenRequestContext tokenRequestContext;
+ private readonly TimeSpan margin;
+ private readonly CancellationToken cancellationToken;
+
+ public AccessTokenCacheTests()
+ {
+ mockCredential = new Mock();
+ tokenRequestContext = new TokenRequestContext(new[] { "https://durabletask.azure.com/.default" });
+ margin = TimeSpan.FromMinutes(5);
+ cancellationToken = CancellationToken.None;
+ }
+
+ [Fact]
+ public async Task GetTokenAsync_WhenCalled_ShouldReturnToken()
+ {
+ // Arrange
+ var expectedToken = new AccessToken("test-token", DateTimeOffset.UtcNow.AddHours(1));
+ mockCredential.Setup(c => c.GetTokenAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(expectedToken);
+ var cache = new AccessTokenCache(mockCredential.Object, tokenRequestContext, margin);
+
+ // Act
+ var token = await cache.GetTokenAsync(cancellationToken);
+
+ // Assert
+ token.Should().Be(expectedToken);
+ mockCredential.Verify(c => c.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Once);
+ }
+
+ [Fact]
+ public async Task GetTokenAsync_WhenTokenExpired_ShouldRequestNewToken()
+ {
+ // Arrange
+ var expiredToken = new AccessToken("expired-token", DateTimeOffset.UtcNow.AddMinutes(-5));
+ var newToken = new AccessToken("new-token", DateTimeOffset.UtcNow.AddHours(1));
+ var cache = new AccessTokenCache(mockCredential.Object, tokenRequestContext, margin);
+
+ mockCredential.SetupSequence(c => c.GetTokenAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(expiredToken)
+ .ReturnsAsync(newToken);
+
+ // Act
+ var firstToken = await cache.GetTokenAsync(cancellationToken);
+ var secondToken = await cache.GetTokenAsync(cancellationToken);
+
+ // Assert
+ firstToken.Should().Be(expiredToken);
+ secondToken.Should().Be(newToken);
+ mockCredential.Verify(c => c.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Exactly(2));
+ }
+
+ [Fact]
+ public async Task GetTokenAsync_WhenTokenValid_ShouldReturnCachedToken()
+ {
+ // Arrange
+ var validToken = new AccessToken("valid-token", DateTimeOffset.UtcNow.AddHours(1));
+ mockCredential.Setup(c => c.GetTokenAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(validToken);
+ var cache = new AccessTokenCache(mockCredential.Object, tokenRequestContext, margin);
+
+ // Act
+ var firstToken = await cache.GetTokenAsync(cancellationToken);
+ var secondToken = await cache.GetTokenAsync(cancellationToken);
+
+ // Assert
+ firstToken.Should().Be(validToken);
+ secondToken.Should().Be(validToken);
+ mockCredential.Verify(c => c.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Once);
+ }
+
+ [Fact]
+ public async Task Constructor_WithNullCredential_ShouldThrowNullReferenceException()
+ {
+ // Arrange
+ var cache = new AccessTokenCache(null!, tokenRequestContext, margin);
+
+ // Act & Assert
+ // TODO: The constructor should validate its parameters and throw ArgumentNullException,
+ // but currently it allows null parameters and throws NullReferenceException when used.
+ var action = () => cache.GetTokenAsync(cancellationToken);
+ await action.Should().ThrowAsync();
+ }
+
+ [Fact]
+ public async Task GetTokenAsync_WhenTokenNearExpiry_ShouldRequestNewToken()
+ {
+ // Arrange
+ var expiryTime = DateTimeOffset.UtcNow.AddMinutes(10);
+ var nearExpiryToken = new AccessToken("near-expiry-token", expiryTime);
+ var newToken = new AccessToken("new-token", expiryTime.AddHours(1));
+ var cache = new AccessTokenCache(mockCredential.Object, tokenRequestContext, TimeSpan.FromMinutes(15));
+
+ mockCredential.SetupSequence(c => c.GetTokenAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(nearExpiryToken)
+ .ReturnsAsync(newToken);
+
+ // Act
+ var firstToken = await cache.GetTokenAsync(cancellationToken);
+ var secondToken = await cache.GetTokenAsync(cancellationToken);
+
+ // Assert
+ firstToken.Should().Be(nearExpiryToken);
+ secondToken.Should().Be(newToken);
+ mockCredential.Verify(c => c.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Exactly(2));
+ }
+}
From 3dfbb7250b879d7b320fb6e246a1fe63378b5163 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sun, 12 Jan 2025 02:59:20 -0800
Subject: [PATCH 37/58] split
---
Microsoft.DurableTask.sln | 22 ++-
.../AzureManaged/Client.AzureManaged.csproj} | 8 +-
.../DurableTaskSchedulerClientExtensions.cs} | 83 +-------
.../AzureManaged}/AccessTokenCache.cs | 4 +-
.../DurableTaskSchedulerConnectionString.cs | 3 +-
.../DurableTaskSchedulerOptions.cs | 30 +--
.../AzureManaged/Shared.AzureManaged.csproj | 20 ++
.../DurableTaskSchedulerWorkerExtensions.cs | 94 +++++++++
.../AzureManaged/Worker.AzureManaged.csproj | 21 ++
.../AzureManaged/Client.AzureManaged.csproj | 26 +++
...rableTaskSchedulerClientExtensionsTests.cs | 182 ++++++++++++++++++
.../AzureManaged}/AccessTokenCacheTests.cs | 3 +-
...rableTaskSchedulerConnectionStringTests.cs | 2 +-
.../DurableTaskSchedulerOptionsTests.cs | 2 +-
.../AzureManaged/Shared.AzureManaged.csproj} | 7 +-
...ableTaskSchedulerWorkerExtensionsTests.cs} | 142 ++------------
.../AzureManaged/Worker.AzureManaged.csproj | 26 +++
17 files changed, 424 insertions(+), 251 deletions(-)
rename src/{Extensions/Azure/Azure.csproj => Client/AzureManaged/Client.AzureManaged.csproj} (76%)
rename src/{Extensions/Azure/DurableTaskSchedulerExtensions.cs => Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs} (51%)
rename src/{Extensions/Azure => Shared/AzureManaged}/AccessTokenCache.cs (94%)
rename src/{Extensions/Azure => Shared/AzureManaged}/DurableTaskSchedulerConnectionString.cs (98%)
rename src/{Extensions/Azure => Shared/AzureManaged}/DurableTaskSchedulerOptions.cs (96%)
create mode 100644 src/Shared/AzureManaged/Shared.AzureManaged.csproj
create mode 100644 src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
create mode 100644 src/Worker/AzureManaged/Worker.AzureManaged.csproj
create mode 100644 test/Client/AzureManaged/Client.AzureManaged.csproj
create mode 100644 test/Client/AzureManaged/DurableTaskSchedulerClientExtensionsTests.cs
rename test/{Extensions/Azure => Shared/AzureManaged}/AccessTokenCacheTests.cs (97%)
rename test/{Extensions/Azure => Shared/AzureManaged}/DurableTaskSchedulerConnectionStringTests.cs (99%)
rename test/{Extensions/Azure => Shared/AzureManaged}/DurableTaskSchedulerOptionsTests.cs (99%)
rename test/{Extensions/Azure/Azure.Tests.csproj => Shared/AzureManaged/Shared.AzureManaged.csproj} (69%)
rename test/{Extensions/Azure/DurableTaskSchedulerExtensionsTests.cs => Worker/AzureManaged/DurableTaskSchedulerWorkerExtensionsTests.cs} (54%)
create mode 100644 test/Worker/AzureManaged/Worker.AzureManaged.csproj
diff --git a/Microsoft.DurableTask.sln b/Microsoft.DurableTask.sln
index 4e5a4451..45dae8aa 100644
--- a/Microsoft.DurableTask.sln
+++ b/Microsoft.DurableTask.sln
@@ -71,11 +71,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Analyzers.Tests", "test\Ana
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsApp.Tests", "samples\AzureFunctionsUnitTests\AzureFunctionsApp.Tests.csproj", "{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{5227C712-2355-403F-90D6-51D0BCAE4D38}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Tests", "test\Extensions\Azure\Azure.Tests.csproj", "{DBB5DB4E-A1B0-4C86-A233-213789C46929}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure", "src\Extensions\Azure\Azure.csproj", "{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.AzureManaged", "src\Shared\AzureManaged\Shared.AzureManaged.csproj", "{82D06CA7-90B3-4791-9CAB-222F1AA113D5}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Tests", "test\Extensions\Azure\Azure.Tests.csproj", "{DBB5DB4E-A1B0-4C86-A233-213789C46929}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.AzureManaged", "test\Shared\AzureManaged\Shared.AzureManaged.csproj", "{F855ACBF-2A6A-4A43-A9E8-51B8599A539E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -191,14 +191,18 @@ Global
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Release|Any CPU.Build.0 = Release|Any CPU
- {662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Release|Any CPU.Build.0 = Release|Any CPU
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Release|Any CPU.Build.0 = Release|Any CPU
+ {82D06CA7-90B3-4791-9CAB-222F1AA113D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {82D06CA7-90B3-4791-9CAB-222F1AA113D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {82D06CA7-90B3-4791-9CAB-222F1AA113D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {82D06CA7-90B3-4791-9CAB-222F1AA113D5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F855ACBF-2A6A-4A43-A9E8-51B8599A539E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F855ACBF-2A6A-4A43-A9E8-51B8599A539E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F855ACBF-2A6A-4A43-A9E8-51B8599A539E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F855ACBF-2A6A-4A43-A9E8-51B8599A539E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -234,9 +238,9 @@ Global
{998E9D97-BD36-4A9D-81FC-5DAC1CE40083} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
{541FCCCE-1059-4691-B027-F761CD80DE92} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
- {5227C712-2355-403F-90D6-51D0BCAE4D38} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
- {662BF73D-A4DD-4910-8625-7C12F1ACDBEC} = {5227C712-2355-403F-90D6-51D0BCAE4D38}
{DBB5DB4E-A1B0-4C86-A233-213789C46929} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
+ {82D06CA7-90B3-4791-9CAB-222F1AA113D5} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
+ {F855ACBF-2A6A-4A43-A9E8-51B8599A539E} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
diff --git a/src/Extensions/Azure/Azure.csproj b/src/Client/AzureManaged/Client.AzureManaged.csproj
similarity index 76%
rename from src/Extensions/Azure/Azure.csproj
rename to src/Client/AzureManaged/Client.AzureManaged.csproj
index d82dd02f..6d0fc498 100644
--- a/src/Extensions/Azure/Azure.csproj
+++ b/src/Client/AzureManaged/Client.AzureManaged.csproj
@@ -2,7 +2,7 @@
net6.0
- Azure extensions for the Durable Task Framework.
+ Azure Managed extensions for the Durable Task Framework client.
true
@@ -14,15 +14,13 @@
-
-
+
-
+
-
diff --git a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
similarity index 51%
rename from src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
rename to src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
index 6692a6a3..a4e9adc4 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerExtensions.cs
+++ b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
@@ -1,82 +1,20 @@
-// Copyright (c) Microsoft Corporation.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Azure.Core;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.Grpc;
-using Microsoft.DurableTask.Worker;
-using Microsoft.DurableTask.Worker.Grpc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
-namespace Microsoft.DurableTask.Extensions.Azure;
+namespace Microsoft.DurableTask.Client.AzureManaged;
///
-/// Extension methods for configuring Durable Task workers and clients to use the Azure Durable Task Scheduler service.
+/// Extension methods for configuring Durable Task clients to use the Azure Durable Task Scheduler service.
///
-public static class DurableTaskSchedulerExtensions
+public static class DurableTaskSchedulerClientExtensions
{
- ///
- /// Configures Durable Task worker to use the Azure Durable Task Scheduler service.
- ///
- /// The Durable Task worker builder to configure.
- /// The endpoint address of the Durable Task Scheduler resource. Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".
- /// The name of the task hub resource associated with the Durable Task Scheduler resource.
- /// The credential used to authenticate with the Durable Task Scheduler task hub resource.
- /// Optional callback to dynamically configure DurableTaskSchedulerOptions.
- public static void UseDurableTaskScheduler(
- this IDurableTaskWorkerBuilder builder,
- string endpointAddress,
- string taskHubName,
- TokenCredential credential,
- Action? configure = null)
- {
- builder.Services.AddOptions(builder.Name)
- .Configure(options =>
- {
- options.EndpointAddress = endpointAddress;
- options.TaskHubName = taskHubName;
- options.Credential = credential;
- })
- .Configure(configure ?? (_ => { }))
- .ValidateDataAnnotations()
- .ValidateOnStart();
-
- builder.Services.TryAddEnumerable(
- ServiceDescriptor.Singleton, ConfigureGrpcChannel>());
- builder.UseGrpc(_ => { });
- }
-
- ///
- /// Configures Durable Task worker to use the Azure Durable Task Scheduler service using a connection string.
- ///
- /// The Durable Task worker builder to configure.
- /// The connection string used to connect to the Durable Task Scheduler service.
- /// Optional callback to dynamically configure DurableTaskSchedulerOptions.
- public static void UseDurableTaskScheduler(
- this IDurableTaskWorkerBuilder builder,
- string connectionString,
- Action? configure = null)
- {
- var connectionOptions = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
-
- builder.Services.AddOptions(builder.Name)
- .Configure(options =>
- {
- options.EndpointAddress = connectionOptions.EndpointAddress;
- options.TaskHubName = connectionOptions.TaskHubName;
- options.Credential = connectionOptions.Credential;
- })
- .Configure(configure ?? (_ => { }))
- .ValidateDataAnnotations()
- .ValidateOnStart();
-
- builder.Services.TryAddEnumerable(
- ServiceDescriptor.Singleton, ConfigureGrpcChannel>());
- builder.UseGrpc(_ => { });
- }
-
///
/// Configures Durable Task client to use the Azure Durable Task Scheduler service.
///
@@ -138,28 +76,19 @@ public static void UseDurableTaskScheduler(
}
///
- /// Internal configuration class that sets up gRPC channels for both worker and client options
+ /// Internal configuration class that sets up gRPC channels for client options
/// using the provided Durable Task Scheduler options.
///
/// Monitor for accessing the current scheduler options configuration.
internal class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
- IConfigureNamedOptions,
IConfigureNamedOptions
{
- public void Configure(GrpcDurableTaskWorkerOptions options) => this.Configure(Options.DefaultName, options);
-
public void Configure(GrpcDurableTaskClientOptions options) => this.Configure(Options.DefaultName, options);
- public void Configure(string? name, GrpcDurableTaskWorkerOptions options)
- {
- DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
- options.Channel = source.CreateChannel();
- }
-
public void Configure(string? name, GrpcDurableTaskClientOptions options)
{
DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
options.Channel = source.CreateChannel();
}
}
-}
+}
diff --git a/src/Extensions/Azure/AccessTokenCache.cs b/src/Shared/AzureManaged/AccessTokenCache.cs
similarity index 94%
rename from src/Extensions/Azure/AccessTokenCache.cs
rename to src/Shared/AzureManaged/AccessTokenCache.cs
index 0a273170..bb4305c6 100644
--- a/src/Extensions/Azure/AccessTokenCache.cs
+++ b/src/Shared/AzureManaged/AccessTokenCache.cs
@@ -3,12 +3,12 @@
using Azure.Core;
-namespace Microsoft.DurableTask.Extensions.Azure;
+namespace Microsoft.DurableTask.Shared.AzureManaged;
///
/// Caches and manages refresh for Azure access tokens.
///
-internal sealed class AccessTokenCache
+sealed class AccessTokenCache
{
readonly TokenCredential credential;
readonly TokenRequestContext context;
diff --git a/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs b/src/Shared/AzureManaged/DurableTaskSchedulerConnectionString.cs
similarity index 98%
rename from src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
rename to src/Shared/AzureManaged/DurableTaskSchedulerConnectionString.cs
index 9002f77b..c1077405 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerConnectionString.cs
+++ b/src/Shared/AzureManaged/DurableTaskSchedulerConnectionString.cs
@@ -1,9 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-
using System.Data.Common;
-namespace Microsoft.DurableTask.Extensions.Azure;
+namespace Microsoft.DurableTask.Shared.AzureManaged;
///
/// Represents the constituent parts of a connection string for a Durable Task Scheduler service.
diff --git a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs b/src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs
similarity index 96%
rename from src/Extensions/Azure/DurableTaskSchedulerOptions.cs
rename to src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs
index 25f994dd..9878e624 100644
--- a/src/Extensions/Azure/DurableTaskSchedulerOptions.cs
+++ b/src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs
@@ -1,13 +1,11 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Azure.Core;
using Azure.Identity;
-using Grpc.Core;
-namespace Microsoft.DurableTask.Extensions.Azure;
+namespace Microsoft.DurableTask.Shared.AzureManaged;
///
/// Options for configuring the Durable Task Scheduler.
@@ -53,6 +51,7 @@ public class DurableTaskSchedulerOptions
///
/// Creates a new instance of from a connection string.
///
+ ///
public static DurableTaskSchedulerOptions FromConnectionString(string connectionString)
{
return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
@@ -62,12 +61,10 @@ internal GrpcChannel CreateChannel()
{
Check.NotNullOrEmpty(this.EndpointAddress, nameof(this.EndpointAddress));
Check.NotNullOrEmpty(this.TaskHubName, nameof(this.TaskHubName));
-
string taskHubName = this.TaskHubName;
string endpoint = !this.EndpointAddress.Contains("://")
? $"https://{this.EndpointAddress}"
: this.EndpointAddress;
-
AccessTokenCache? cache =
this.Credential is not null
? new AccessTokenCache(
@@ -75,13 +72,11 @@ this.Credential is not null
new TokenRequestContext(new[] { $"{this.ResourceId}/.default" }),
TimeSpan.FromMinutes(5))
: null;
-
CallCredentials managedBackendCreds = CallCredentials.FromInterceptor(
async (context, metadata) =>
{
metadata.Add("taskhub", taskHubName);
metadata.Add("workerid", this.WorkerId);
-
if (cache == null)
{
return;
@@ -95,7 +90,6 @@ this.Credential is not null
ChannelCredentials channelCreds = endpoint.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ?
ChannelCredentials.SecureSsl :
ChannelCredentials.Insecure;
-
return GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
{
Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
@@ -106,6 +100,7 @@ this.Credential is not null
///
/// Creates a new instance of from a parsed connection string.
///
+ ///
public static DurableTaskSchedulerOptions FromConnectionString(
DurableTaskSchedulerConnectionString connectionString)
{
@@ -113,9 +108,8 @@ public static DurableTaskSchedulerOptions FromConnectionString(
{
EndpointAddress = connectionString.Endpoint,
TaskHubName = connectionString.TaskHubName,
- Credential = GetCredentialFromConnectionString(connectionString)
+ Credential = GetCredentialFromConnectionString(connectionString),
};
-
return options;
}
@@ -128,20 +122,20 @@ public static DurableTaskSchedulerOptions FromConnectionString(
{
case "defaultazure":
return new DefaultAzureCredential();
-
case "managedidentity":
return new ManagedIdentityCredential(connectionString.ClientId);
-
case "workloadidentity":
var opts = new WorkloadIdentityCredentialOptions();
if (!string.IsNullOrEmpty(connectionString.ClientId))
{
opts.ClientId = connectionString.ClientId;
}
+
if (!string.IsNullOrEmpty(connectionString.TenantId))
{
opts.TenantId = connectionString.TenantId;
}
+
if (connectionString.AdditionallyAllowedTenants is not null)
{
foreach (string tenant in connectionString.AdditionallyAllowedTenants)
@@ -149,24 +143,20 @@ public static DurableTaskSchedulerOptions FromConnectionString(
opts.AdditionallyAllowedTenants.Add(tenant);
}
}
- return new WorkloadIdentityCredential(opts);
+ return new WorkloadIdentityCredential(opts);
case "environment":
return new EnvironmentCredential();
-
case "azurecli":
return new AzureCliCredential();
-
case "azurepowershell":
return new AzurePowerShellCredential();
-
case "none":
return null;
-
default:
throw new ArgumentException(
$"The connection string contains an unsupported authentication type '{authType}'.",
nameof(connectionString));
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Shared/AzureManaged/Shared.AzureManaged.csproj b/src/Shared/AzureManaged/Shared.AzureManaged.csproj
new file mode 100644
index 00000000..e90c2dc4
--- /dev/null
+++ b/src/Shared/AzureManaged/Shared.AzureManaged.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net6.0
+ Shared components for Azure Managed extensions for the Durable Task Framework.
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
new file mode 100644
index 00000000..fc034e87
--- /dev/null
+++ b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
@@ -0,0 +1,94 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Azure.Core;
+using Microsoft.DurableTask.Worker;
+using Microsoft.DurableTask.Worker.Grpc;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.DurableTask.Worker.AzureManaged;
+
+///
+/// Extension methods for configuring Durable Task workers to use the Azure Durable Task Scheduler service.
+///
+public static class DurableTaskSchedulerWorkerExtensions
+{
+ ///
+ /// Configures Durable Task worker to use the Azure Durable Task Scheduler service.
+ ///
+ /// The Durable Task worker builder to configure.
+ /// The endpoint address of the Durable Task Scheduler resource. Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".
+ /// The name of the task hub resource associated with the Durable Task Scheduler resource.
+ /// The credential used to authenticate with the Durable Task Scheduler task hub resource.
+ /// Optional callback to dynamically configure DurableTaskSchedulerOptions.
+ public static void UseDurableTaskScheduler(
+ this IDurableTaskWorkerBuilder builder,
+ string endpointAddress,
+ string taskHubName,
+ TokenCredential credential,
+ Action? configure = null)
+ {
+ builder.Services.AddOptions(builder.Name)
+ .Configure(options =>
+ {
+ options.EndpointAddress = endpointAddress;
+ options.TaskHubName = taskHubName;
+ options.Credential = credential;
+ })
+ .Configure(configure ?? (_ => { }))
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+
+ builder.Services.TryAddEnumerable(
+ ServiceDescriptor.Singleton, ConfigureGrpcChannel>());
+ builder.UseGrpc(_ => { });
+ }
+
+ ///
+ /// Configures Durable Task worker to use the Azure Durable Task Scheduler service using a connection string.
+ ///
+ /// The Durable Task worker builder to configure.
+ /// The connection string used to connect to the Durable Task Scheduler service.
+ /// Optional callback to dynamically configure DurableTaskSchedulerOptions.
+ public static void UseDurableTaskScheduler(
+ this IDurableTaskWorkerBuilder builder,
+ string connectionString,
+ Action? configure = null)
+ {
+ var connectionOptions = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+
+ builder.Services.AddOptions(builder.Name)
+ .Configure(options =>
+ {
+ options.EndpointAddress = connectionOptions.EndpointAddress;
+ options.TaskHubName = connectionOptions.TaskHubName;
+ options.Credential = connectionOptions.Credential;
+ })
+ .Configure(configure ?? (_ => { }))
+ .ValidateDataAnnotations()
+ .ValidateOnStart();
+
+ builder.Services.TryAddEnumerable(
+ ServiceDescriptor.Singleton, ConfigureGrpcChannel>());
+ builder.UseGrpc(_ => { });
+ }
+
+ ///
+ /// Internal configuration class that sets up gRPC channels for worker options
+ /// using the provided Durable Task Scheduler options.
+ ///
+ /// Monitor for accessing the current scheduler options configuration.
+ internal class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
+ IConfigureNamedOptions
+ {
+ public void Configure(GrpcDurableTaskWorkerOptions options) => this.Configure(Options.DefaultName, options);
+
+ public void Configure(string? name, GrpcDurableTaskWorkerOptions options)
+ {
+ DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
+ options.Channel = source.CreateChannel();
+ }
+ }
+}
diff --git a/src/Worker/AzureManaged/Worker.AzureManaged.csproj b/src/Worker/AzureManaged/Worker.AzureManaged.csproj
new file mode 100644
index 00000000..ffc8e101
--- /dev/null
+++ b/src/Worker/AzureManaged/Worker.AzureManaged.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net6.0
+ Azure Managed extensions for the Durable Task Framework worker.
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Client/AzureManaged/Client.AzureManaged.csproj b/test/Client/AzureManaged/Client.AzureManaged.csproj
new file mode 100644
index 00000000..0cd156c5
--- /dev/null
+++ b/test/Client/AzureManaged/Client.AzureManaged.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net6.0
+ enable
+ enable
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Client/AzureManaged/DurableTaskSchedulerClientExtensionsTests.cs b/test/Client/AzureManaged/DurableTaskSchedulerClientExtensionsTests.cs
new file mode 100644
index 00000000..5a0099dd
--- /dev/null
+++ b/test/Client/AzureManaged/DurableTaskSchedulerClientExtensionsTests.cs
@@ -0,0 +1,182 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Azure.Core;
+using Azure.Identity;
+using FluentAssertions;
+using Grpc.Net.Client;
+using Microsoft.DurableTask.Client;
+using Microsoft.DurableTask.Client.Grpc;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Moq;
+using System.ComponentModel.DataAnnotations;
+using Xunit;
+
+namespace Microsoft.DurableTask.Client.AzureManaged.Tests;
+
+public class DurableTaskSchedulerClientExtensionsTests
+{
+ private const string ValidEndpoint = "myaccount.westus3.durabletask.io";
+ private const string ValidTaskHub = "testhub";
+
+ [Fact]
+ public void UseDurableTaskScheduler_WithEndpointAndCredential_ShouldConfigureCorrectly()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ var credential = new DefaultAzureCredential();
+
+ // Act
+ mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential);
+
+ // Assert
+ var provider = services.BuildServiceProvider();
+ var options = provider.GetService>();
+ options.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void UseDurableTaskScheduler_WithConnectionString_ShouldConfigureCorrectly()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
+
+ // Act
+ mockBuilder.Object.UseDurableTaskScheduler(connectionString);
+
+ // Assert
+ var provider = services.BuildServiceProvider();
+ var options = provider.GetService>();
+ options.Should().NotBeNull();
+ }
+
+ [Theory]
+ [InlineData(null, "testhub")]
+ [InlineData("myaccount.westus3.durabletask.io", null)]
+ public void UseDurableTaskScheduler_WithNullParameters_ShouldThrowArgumentNullException(string endpoint, string taskHub)
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ var credential = new DefaultAzureCredential();
+
+ // Act
+ var action = () => mockBuilder.Object.UseDurableTaskScheduler(endpoint, taskHub, credential);
+
+ // Assert
+ action.Should().NotThrow(); // The validation happens when building the service provider
+
+ if (endpoint == null || taskHub == null)
+ {
+ var provider = services.BuildServiceProvider();
+ var ex = Assert.Throws(() =>
+ {
+ var options = provider.GetRequiredService>().Value;
+ });
+ Assert.Contains(endpoint == null ? "EndpointAddress" : "TaskHubName", ex.Message);
+ }
+ }
+
+ [Fact]
+ public void UseDurableTaskScheduler_WithNullCredential_ShouldSucceed()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ TokenCredential? credential = null;
+
+ // Act & Assert
+ var action = () => mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential!);
+ action.Should().NotThrow();
+ }
+
+ [Fact]
+ public void UseDurableTaskScheduler_WithInvalidConnectionString_ShouldThrowArgumentException()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ var connectionString = "This is not a valid=connection string format";
+
+ // Act
+ var action = () => mockBuilder.Object.UseDurableTaskScheduler(connectionString);
+
+ // Assert
+ action.Should().Throw()
+ .WithMessage("Value cannot be null. (Parameter 'The connection string is missing the required 'Endpoint' property.')");
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(null)]
+ public void UseDurableTaskScheduler_WithNullOrEmptyConnectionString_ShouldThrowArgumentException(string connectionString)
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+
+ // Act & Assert
+ var action = () => mockBuilder.Object.UseDurableTaskScheduler(connectionString);
+ action.Should().Throw();
+ }
+
+ [Fact]
+ public void UseDurableTaskScheduler_WithNamedOptions_ShouldConfigureCorrectly()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ var mockBuilder = new Mock();
+ mockBuilder.Setup(b => b.Services).Returns(services);
+ mockBuilder.Setup(b => b.Name).Returns("CustomName");
+ var credential = new DefaultAzureCredential();
+
+ // Act
+ mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential);
+
+ // Assert
+ var provider = services.BuildServiceProvider();
+ var optionsMonitor = provider.GetService>();
+ optionsMonitor.Should().NotBeNull();
+ var options = optionsMonitor!.Get("CustomName");
+ options.Should().NotBeNull();
+ options.EndpointAddress.Should().Be(ValidEndpoint); // The https:// prefix is added by CreateChannel, not in the extension method
+ options.TaskHubName.Should().Be(ValidTaskHub);
+ options.Credential.Should().BeOfType();
+ }
+
+ [Fact]
+ public void ConfigureGrpcChannel_ShouldConfigureClientOptions()
+ {
+ // Arrange
+ var services = new ServiceCollection();
+ services.AddOptions()
+ .Configure(options =>
+ {
+ options.EndpointAddress = $"https://{ValidEndpoint}";
+ options.TaskHubName = ValidTaskHub;
+ options.Credential = new DefaultAzureCredential();
+ });
+
+ var provider = services.BuildServiceProvider();
+ var schedulerOptions = provider.GetRequiredService>();
+ var configureGrpcChannel = new DurableTaskSchedulerClientExtensions.ConfigureGrpcChannel(schedulerOptions);
+
+ // Act
+ var clientOptions = new GrpcDurableTaskClientOptions();
+ configureGrpcChannel.Configure(clientOptions);
+
+ // Assert
+ clientOptions.Channel.Should().NotBeNull();
+ clientOptions.Channel.Should().BeOfType();
+ }
+}
diff --git a/test/Extensions/Azure/AccessTokenCacheTests.cs b/test/Shared/AzureManaged/AccessTokenCacheTests.cs
similarity index 97%
rename from test/Extensions/Azure/AccessTokenCacheTests.cs
rename to test/Shared/AzureManaged/AccessTokenCacheTests.cs
index 8c766010..525342b6 100644
--- a/test/Extensions/Azure/AccessTokenCacheTests.cs
+++ b/test/Shared/AzureManaged/AccessTokenCacheTests.cs
@@ -3,11 +3,10 @@
using System.Threading.Tasks;
using Azure.Core;
using FluentAssertions;
-using Microsoft.DurableTask.Extensions.Azure;
using Moq;
using Xunit;
-namespace Microsoft.DurableTask.Tests.Extensions.Azure;
+namespace Microsoft.DurableTask.Shared.AzureManaged.Tests;
public class AccessTokenCacheTests
{
diff --git a/test/Extensions/Azure/DurableTaskSchedulerConnectionStringTests.cs b/test/Shared/AzureManaged/DurableTaskSchedulerConnectionStringTests.cs
similarity index 99%
rename from test/Extensions/Azure/DurableTaskSchedulerConnectionStringTests.cs
rename to test/Shared/AzureManaged/DurableTaskSchedulerConnectionStringTests.cs
index 75372d6d..bf5631f5 100644
--- a/test/Extensions/Azure/DurableTaskSchedulerConnectionStringTests.cs
+++ b/test/Shared/AzureManaged/DurableTaskSchedulerConnectionStringTests.cs
@@ -5,7 +5,7 @@
using System.Data.Common;
using Xunit;
-namespace Microsoft.DurableTask.Extensions.Azure.Tests;
+namespace Microsoft.DurableTask.Shared.AzureManaged.Tests;
public class DurableTaskSchedulerConnectionStringTests
{
diff --git a/test/Extensions/Azure/DurableTaskSchedulerOptionsTests.cs b/test/Shared/AzureManaged/DurableTaskSchedulerOptionsTests.cs
similarity index 99%
rename from test/Extensions/Azure/DurableTaskSchedulerOptionsTests.cs
rename to test/Shared/AzureManaged/DurableTaskSchedulerOptionsTests.cs
index 7dc0c591..4c266cc1 100644
--- a/test/Extensions/Azure/DurableTaskSchedulerOptionsTests.cs
+++ b/test/Shared/AzureManaged/DurableTaskSchedulerOptionsTests.cs
@@ -6,7 +6,7 @@
using FluentAssertions;
using Xunit;
-namespace Microsoft.DurableTask.Extensions.Azure.Tests;
+namespace Microsoft.DurableTask.Shared.AzureManaged.Tests;
public class DurableTaskSchedulerOptionsTests
{
diff --git a/test/Extensions/Azure/Azure.Tests.csproj b/test/Shared/AzureManaged/Shared.AzureManaged.csproj
similarity index 69%
rename from test/Extensions/Azure/Azure.Tests.csproj
rename to test/Shared/AzureManaged/Shared.AzureManaged.csproj
index 53146103..3eeeacb7 100644
--- a/test/Extensions/Azure/Azure.Tests.csproj
+++ b/test/Shared/AzureManaged/Shared.AzureManaged.csproj
@@ -8,11 +8,12 @@
+
-
-
+
+
-
+
diff --git a/test/Extensions/Azure/DurableTaskSchedulerExtensionsTests.cs b/test/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensionsTests.cs
similarity index 54%
rename from test/Extensions/Azure/DurableTaskSchedulerExtensionsTests.cs
rename to test/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensionsTests.cs
index 4bd73bfa..2dd901b9 100644
--- a/test/Extensions/Azure/DurableTaskSchedulerExtensionsTests.cs
+++ b/test/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensionsTests.cs
@@ -5,8 +5,6 @@
using Azure.Identity;
using FluentAssertions;
using Grpc.Net.Client;
-using Microsoft.DurableTask.Client;
-using Microsoft.DurableTask.Client.Grpc;
using Microsoft.DurableTask.Worker;
using Microsoft.DurableTask.Worker.Grpc;
using Microsoft.Extensions.DependencyInjection;
@@ -15,15 +13,15 @@
using System.ComponentModel.DataAnnotations;
using Xunit;
-namespace Microsoft.DurableTask.Extensions.Azure.Tests;
+namespace Microsoft.DurableTask.Worker.AzureManaged.Tests;
-public class DurableTaskSchedulerExtensionsTests
+public class DurableTaskSchedulerWorkerExtensionsTests
{
private const string ValidEndpoint = "myaccount.westus3.durabletask.io";
private const string ValidTaskHub = "testhub";
[Fact]
- public void UseDurableTaskScheduler_Worker_WithEndpointAndCredential_ShouldConfigureCorrectly()
+ public void UseDurableTaskScheduler_WithEndpointAndCredential_ShouldConfigureCorrectly()
{
// Arrange
var services = new ServiceCollection();
@@ -34,24 +32,6 @@ public void UseDurableTaskScheduler_Worker_WithEndpointAndCredential_ShouldConfi
// Act
mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential);
- // Assert - Verify that the options were registered
- var provider = services.BuildServiceProvider();
- var options = provider.GetService>();
- options.Should().NotBeNull();
- }
-
- [Fact]
- public void UseDurableTaskScheduler_Worker_WithConnectionString_ShouldConfigureCorrectly()
- {
- // Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
- mockBuilder.Setup(b => b.Services).Returns(services);
- string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
-
- // Act
- mockBuilder.Object.UseDurableTaskScheduler(connectionString);
-
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetService>();
@@ -59,58 +39,17 @@ public void UseDurableTaskScheduler_Worker_WithConnectionString_ShouldConfigureC
}
[Fact]
- public void UseDurableTaskScheduler_Client_WithEndpointAndCredential_ShouldConfigureCorrectly()
+ public void UseDurableTaskScheduler_WithConnectionString_ShouldConfigureCorrectly()
{
// Arrange
var services = new ServiceCollection();
- var mockBuilder = new Mock();
- mockBuilder.Setup(b => b.Services).Returns(services);
- var credential = new DefaultAzureCredential();
-
- // Act
- mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential);
-
- // Assert
- var provider = services.BuildServiceProvider();
- var options = provider.GetService>();
- options.Should().NotBeNull();
- }
-
- [Fact]
- public void UseDurableTaskScheduler_Client_WithConnectionString_ShouldConfigureCorrectly()
- {
- // Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
+ var mockBuilder = new Mock();
mockBuilder.Setup(b => b.Services).Returns(services);
string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
// Act
mockBuilder.Object.UseDurableTaskScheduler(connectionString);
- // Assert
- var provider = services.BuildServiceProvider();
- var options = provider.GetService>();
- options.Should().NotBeNull();
- }
-
- [Fact]
- public void UseDurableTaskScheduler_WithOptions_ShouldApplyConfiguration()
- {
- // Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
- mockBuilder.Setup(b => b.Services).Returns(services);
- var credential = new DefaultAzureCredential();
- string workerId = "customWorker";
-
- // Act
- mockBuilder.Object.UseDurableTaskScheduler(
- ValidEndpoint,
- ValidTaskHub,
- credential,
- options => options.WorkerId = workerId);
-
// Assert
var provider = services.BuildServiceProvider();
var options = provider.GetService>();
@@ -192,34 +131,12 @@ public void UseDurableTaskScheduler_WithNullOrEmptyConnectionString_ShouldThrowA
}
[Fact]
- public void UseDurableTaskScheduler_Worker_WithValidationFailure_ShouldThrowValidationException()
+ public void UseDurableTaskScheduler_WithNamedOptions_ShouldConfigureCorrectly()
{
// Arrange
var services = new ServiceCollection();
var mockBuilder = new Mock();
mockBuilder.Setup(b => b.Services).Returns(services);
- var credential = new DefaultAzureCredential();
-
- // Act
- mockBuilder.Object.UseDurableTaskScheduler(string.Empty, ValidTaskHub, credential);
-
- // Assert
- var ex = Assert.Throws(() =>
- {
- var provider = services.BuildServiceProvider();
- var options = provider.GetRequiredService>().Value;
- });
-
- Assert.Contains("EndpointAddress", ex.Message);
- }
-
- [Fact]
- public void UseDurableTaskScheduler_Client_WithNamedOptions_ShouldConfigureCorrectly()
- {
- // Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
- mockBuilder.Setup(b => b.Services).Returns(services);
mockBuilder.Setup(b => b.Name).Returns("CustomName");
var credential = new DefaultAzureCredential();
@@ -238,7 +155,7 @@ public void UseDurableTaskScheduler_Client_WithNamedOptions_ShouldConfigureCorre
}
[Fact]
- public void ConfigureGrpcChannel_ShouldConfigureWorkerAndClientOptions()
+ public void ConfigureGrpcChannel_ShouldConfigureWorkerOptions()
{
// Arrange
var services = new ServiceCollection();
@@ -251,48 +168,15 @@ public void ConfigureGrpcChannel_ShouldConfigureWorkerAndClientOptions()
});
var provider = services.BuildServiceProvider();
- var schedulerOptions = provider.GetService>();
- schedulerOptions.Should().NotBeNull("SchedulerOptions should be registered");
- var configureChannel = new DurableTaskSchedulerExtensions.ConfigureGrpcChannel(schedulerOptions!);
-
- var workerOptions = new GrpcDurableTaskWorkerOptions();
- var clientOptions = new GrpcDurableTaskClientOptions();
+ var schedulerOptions = provider.GetRequiredService>();
+ var configureGrpcChannel = new DurableTaskSchedulerWorkerExtensions.ConfigureGrpcChannel(schedulerOptions);
// Act
- configureChannel.Configure(Options.DefaultName, workerOptions);
- configureChannel.Configure(Options.DefaultName, clientOptions);
+ var workerOptions = new GrpcDurableTaskWorkerOptions();
+ configureGrpcChannel.Configure(workerOptions);
// Assert
workerOptions.Channel.Should().NotBeNull();
- clientOptions.Channel.Should().NotBeNull();
- }
-
- [Fact]
- public void UseDurableTaskScheduler_WithCustomConfiguration_ShouldApplyConfiguration()
- {
- // Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
- mockBuilder.Setup(b => b.Services).Returns(services);
- var credential = new DefaultAzureCredential();
- string customWorkerId = "custom-worker";
-
- // Act
- mockBuilder.Object.UseDurableTaskScheduler(
- ValidEndpoint,
- ValidTaskHub,
- credential,
- options =>
- {
- options.WorkerId = customWorkerId;
- options.AllowInsecureCredentials = true;
- });
-
- // Assert
- var provider = services.BuildServiceProvider();
- var options = provider.GetService>();
- options.Should().NotBeNull();
- options.Value.WorkerId.Should().Be(customWorkerId);
- options.Value.AllowInsecureCredentials.Should().BeTrue();
+ workerOptions.Channel.Should().BeOfType();
}
-}
+}
diff --git a/test/Worker/AzureManaged/Worker.AzureManaged.csproj b/test/Worker/AzureManaged/Worker.AzureManaged.csproj
new file mode 100644
index 00000000..3b38e5d1
--- /dev/null
+++ b/test/Worker/AzureManaged/Worker.AzureManaged.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net6.0
+ enable
+ enable
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 5083703d48b7d26a81369a7376b6b24b7d66ce87 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sun, 12 Jan 2025 11:02:40 -0800
Subject: [PATCH 38/58] fix shared
---
src/Shared/AzureManaged/AccessTokenCache.cs | 2 +-
.../DurableTaskSchedulerConnectionString.cs | 2 +-
.../DurableTaskSchedulerOptions.cs | 38 ++++++++++---------
.../AzureManaged/Shared.AzureManaged.csproj | 5 +++
4 files changed, 27 insertions(+), 20 deletions(-)
diff --git a/src/Shared/AzureManaged/AccessTokenCache.cs b/src/Shared/AzureManaged/AccessTokenCache.cs
index bb4305c6..54091f3c 100644
--- a/src/Shared/AzureManaged/AccessTokenCache.cs
+++ b/src/Shared/AzureManaged/AccessTokenCache.cs
@@ -3,7 +3,7 @@
using Azure.Core;
-namespace Microsoft.DurableTask.Shared.AzureManaged;
+namespace Microsoft.DurableTask;
///
/// Caches and manages refresh for Azure access tokens.
diff --git a/src/Shared/AzureManaged/DurableTaskSchedulerConnectionString.cs b/src/Shared/AzureManaged/DurableTaskSchedulerConnectionString.cs
index c1077405..7d3d18b4 100644
--- a/src/Shared/AzureManaged/DurableTaskSchedulerConnectionString.cs
+++ b/src/Shared/AzureManaged/DurableTaskSchedulerConnectionString.cs
@@ -2,7 +2,7 @@
// Licensed under the MIT License.
using System.Data.Common;
-namespace Microsoft.DurableTask.Shared.AzureManaged;
+namespace Microsoft.DurableTask;
///
/// Represents the constituent parts of a connection string for a Durable Task Scheduler service.
diff --git a/src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs b/src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs
index 9878e624..20a34093 100644
--- a/src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs
+++ b/src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs
@@ -5,7 +5,7 @@
using Azure.Core;
using Azure.Identity;
-namespace Microsoft.DurableTask.Shared.AzureManaged;
+namespace Microsoft.DurableTask;
///
/// Options for configuring the Durable Task Scheduler.
@@ -51,12 +51,30 @@ public class DurableTaskSchedulerOptions
///
/// Creates a new instance of from a connection string.
///
- ///
+ /// The connection string to parse.
+ /// A new instance of .
public static DurableTaskSchedulerOptions FromConnectionString(string connectionString)
{
return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
}
+ ///
+ /// Creates a new instance of from a parsed connection string.
+ ///
+ /// The connection string to parse.
+ /// A new instance of .
+ public static DurableTaskSchedulerOptions FromConnectionString(
+ DurableTaskSchedulerConnectionString connectionString) => new()
+ {
+ EndpointAddress = connectionString.Endpoint,
+ TaskHubName = connectionString.TaskHubName,
+ Credential = GetCredentialFromConnectionString(connectionString),
+ };
+
+ ///
+ /// Creates a gRPC channel for communicating with the Durable Task Scheduler service.
+ ///
+ /// A configured instance that can be used to make gRPC calls.
internal GrpcChannel CreateChannel()
{
Check.NotNullOrEmpty(this.EndpointAddress, nameof(this.EndpointAddress));
@@ -97,22 +115,6 @@ this.Credential is not null
});
}
- ///
- /// Creates a new instance of from a parsed connection string.
- ///
- ///
- public static DurableTaskSchedulerOptions FromConnectionString(
- DurableTaskSchedulerConnectionString connectionString)
- {
- var options = new DurableTaskSchedulerOptions
- {
- EndpointAddress = connectionString.Endpoint,
- TaskHubName = connectionString.TaskHubName,
- Credential = GetCredentialFromConnectionString(connectionString),
- };
- return options;
- }
-
static TokenCredential? GetCredentialFromConnectionString(DurableTaskSchedulerConnectionString connectionString)
{
string authType = connectionString.Authentication;
diff --git a/src/Shared/AzureManaged/Shared.AzureManaged.csproj b/src/Shared/AzureManaged/Shared.AzureManaged.csproj
index e90c2dc4..2ee55cde 100644
--- a/src/Shared/AzureManaged/Shared.AzureManaged.csproj
+++ b/src/Shared/AzureManaged/Shared.AzureManaged.csproj
@@ -17,4 +17,9 @@
+
+
+
+
+
From a50500477cef532536e3f3ef1c0f3bf7d9163dd5 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sun, 12 Jan 2025 11:10:11 -0800
Subject: [PATCH 39/58] save
---
.../AzureManaged/DurableTaskSchedulerClientExtensions.cs | 3 +--
src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs | 2 +-
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
index a4e9adc4..58ac05b7 100644
--- a/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
+++ b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
@@ -2,13 +2,12 @@
// Licensed under the MIT License.
using Azure.Core;
-using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.Grpc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
-namespace Microsoft.DurableTask.Client.AzureManaged;
+namespace Microsoft.DurableTask.Client;
///
/// Extension methods for configuring Durable Task clients to use the Azure Durable Task Scheduler service.
diff --git a/src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs b/src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs
index 20a34093..5a9ce98b 100644
--- a/src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs
+++ b/src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs
@@ -75,7 +75,7 @@ public static DurableTaskSchedulerOptions FromConnectionString(
/// Creates a gRPC channel for communicating with the Durable Task Scheduler service.
///
/// A configured instance that can be used to make gRPC calls.
- internal GrpcChannel CreateChannel()
+ public GrpcChannel CreateChannel()
{
Check.NotNullOrEmpty(this.EndpointAddress, nameof(this.EndpointAddress));
Check.NotNullOrEmpty(this.TaskHubName, nameof(this.TaskHubName));
From 443613ec053938ac69c6e698b69490a1a112b047 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sun, 12 Jan 2025 11:11:32 -0800
Subject: [PATCH 40/58] fix client managed
---
.../DurableTaskSchedulerClientExtensions.cs | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
index 58ac05b7..268617a0 100644
--- a/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
+++ b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
@@ -82,12 +82,21 @@ public static void UseDurableTaskScheduler(
internal class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
IConfigureNamedOptions
{
+ ///
+ /// Configures the default named options instance.
+ ///
+ /// The options instance to configure.
public void Configure(GrpcDurableTaskClientOptions options) => this.Configure(Options.DefaultName, options);
+ ///
+ /// Configures a named options instance.
+ ///
+ /// The name of the options instance to configure.
+ /// The options instance to configure.
public void Configure(string? name, GrpcDurableTaskClientOptions options)
{
DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
options.Channel = source.CreateChannel();
}
}
-}
+}
From 8744f8ab15107249e56a5190ac234312240ed040 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sun, 12 Jan 2025 12:07:32 -0800
Subject: [PATCH 41/58] fix worker
---
src/Client/AzureManaged/Client.AzureManaged.csproj | 14 +-------------
.../DurableTaskSchedulerWorkerExtensions.cs | 11 ++++++++++-
src/Worker/AzureManaged/Worker.AzureManaged.csproj | 7 -------
3 files changed, 11 insertions(+), 21 deletions(-)
diff --git a/src/Client/AzureManaged/Client.AzureManaged.csproj b/src/Client/AzureManaged/Client.AzureManaged.csproj
index 6d0fc498..1cf0934c 100644
--- a/src/Client/AzureManaged/Client.AzureManaged.csproj
+++ b/src/Client/AzureManaged/Client.AzureManaged.csproj
@@ -7,20 +7,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
index fc034e87..b8dff6b4 100644
--- a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
+++ b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
@@ -83,12 +83,21 @@ public static void UseDurableTaskScheduler(
internal class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
IConfigureNamedOptions
{
+ ///
+ /// Configures the default named options instance.
+ ///
+ /// The options instance to configure.
public void Configure(GrpcDurableTaskWorkerOptions options) => this.Configure(Options.DefaultName, options);
+ ///
+ /// Configures a named options instance.
+ ///
+ /// The name of the options instance to configure.
+ /// The options instance to configure.
public void Configure(string? name, GrpcDurableTaskWorkerOptions options)
{
DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
options.Channel = source.CreateChannel();
}
}
-}
+}
diff --git a/src/Worker/AzureManaged/Worker.AzureManaged.csproj b/src/Worker/AzureManaged/Worker.AzureManaged.csproj
index ffc8e101..e8647fa6 100644
--- a/src/Worker/AzureManaged/Worker.AzureManaged.csproj
+++ b/src/Worker/AzureManaged/Worker.AzureManaged.csproj
@@ -7,13 +7,6 @@
-
-
-
-
-
-
-
From 37d3d5e4fa1e37949282be37a4061a75ca72bc0f Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sun, 12 Jan 2025 12:16:27 -0800
Subject: [PATCH 42/58] fix shared tests
---
...ed.AzureManaged.csproj => Shared.AzureManaged.Tests.csproj} | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
rename test/Shared/AzureManaged/{Shared.AzureManaged.csproj => Shared.AzureManaged.Tests.csproj} (87%)
diff --git a/test/Shared/AzureManaged/Shared.AzureManaged.csproj b/test/Shared/AzureManaged/Shared.AzureManaged.Tests.csproj
similarity index 87%
rename from test/Shared/AzureManaged/Shared.AzureManaged.csproj
rename to test/Shared/AzureManaged/Shared.AzureManaged.Tests.csproj
index 3eeeacb7..8174e5c5 100644
--- a/test/Shared/AzureManaged/Shared.AzureManaged.csproj
+++ b/test/Shared/AzureManaged/Shared.AzureManaged.Tests.csproj
@@ -8,12 +8,11 @@
-
-
+
From c7f499aaa1f8855c5f159621b2c4c292698cc43d Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sun, 12 Jan 2025 12:20:53 -0800
Subject: [PATCH 43/58] fix client tests
---
.../Client.AzureManaged.Tests.csproj | 17 ++++++++++++
.../AzureManaged/Client.AzureManaged.csproj | 26 -------------------
2 files changed, 17 insertions(+), 26 deletions(-)
create mode 100644 test/Client/AzureManaged/Client.AzureManaged.Tests.csproj
delete mode 100644 test/Client/AzureManaged/Client.AzureManaged.csproj
diff --git a/test/Client/AzureManaged/Client.AzureManaged.Tests.csproj b/test/Client/AzureManaged/Client.AzureManaged.Tests.csproj
new file mode 100644
index 00000000..e19f0de9
--- /dev/null
+++ b/test/Client/AzureManaged/Client.AzureManaged.Tests.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Client/AzureManaged/Client.AzureManaged.csproj b/test/Client/AzureManaged/Client.AzureManaged.csproj
deleted file mode 100644
index 0cd156c5..00000000
--- a/test/Client/AzureManaged/Client.AzureManaged.csproj
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
- net6.0
- enable
- enable
- false
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
From f077e9840c6bb2c5afc7afff04a5c9364b09aa75 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sun, 12 Jan 2025 12:23:14 -0800
Subject: [PATCH 44/58] fix worker tests
---
.../Worker.AzureManaged.Tests.csproj | 17 ++++++++++++
.../AzureManaged/Worker.AzureManaged.csproj | 26 -------------------
2 files changed, 17 insertions(+), 26 deletions(-)
create mode 100644 test/Worker/AzureManaged/Worker.AzureManaged.Tests.csproj
delete mode 100644 test/Worker/AzureManaged/Worker.AzureManaged.csproj
diff --git a/test/Worker/AzureManaged/Worker.AzureManaged.Tests.csproj b/test/Worker/AzureManaged/Worker.AzureManaged.Tests.csproj
new file mode 100644
index 00000000..6e43cac1
--- /dev/null
+++ b/test/Worker/AzureManaged/Worker.AzureManaged.Tests.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Worker/AzureManaged/Worker.AzureManaged.csproj b/test/Worker/AzureManaged/Worker.AzureManaged.csproj
deleted file mode 100644
index 3b38e5d1..00000000
--- a/test/Worker/AzureManaged/Worker.AzureManaged.csproj
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
- net6.0
- enable
- enable
- false
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
From 89b70711ec44781d6be0e87a9aad99d42acd81c1 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sun, 12 Jan 2025 12:35:12 -0800
Subject: [PATCH 45/58] fix tests path
---
Microsoft.DurableTask.sln | 21 -------------------
.../Client.AzureManaged.Tests.csproj | 0
...rableTaskSchedulerClientExtensionsTests.cs | 0
.../AccessTokenCacheTests.cs | 0
...rableTaskSchedulerConnectionStringTests.cs | 0
.../DurableTaskSchedulerOptionsTests.cs | 0
.../Shared.AzureManaged.Tests.csproj | 0
...rableTaskSchedulerWorkerExtensionsTests.cs | 0
.../Worker.AzureManaged.Tests.csproj | 0
9 files changed, 21 deletions(-)
rename test/Client/{AzureManaged => AzureManaged.Tests}/Client.AzureManaged.Tests.csproj (100%)
rename test/Client/{AzureManaged => AzureManaged.Tests}/DurableTaskSchedulerClientExtensionsTests.cs (100%)
rename test/Shared/{AzureManaged => AzureManaged.Tests}/AccessTokenCacheTests.cs (100%)
rename test/Shared/{AzureManaged => AzureManaged.Tests}/DurableTaskSchedulerConnectionStringTests.cs (100%)
rename test/Shared/{AzureManaged => AzureManaged.Tests}/DurableTaskSchedulerOptionsTests.cs (100%)
rename test/Shared/{AzureManaged => AzureManaged.Tests}/Shared.AzureManaged.Tests.csproj (100%)
rename test/Worker/{AzureManaged => AzureManaged.Tests}/DurableTaskSchedulerWorkerExtensionsTests.cs (100%)
rename test/Worker/{AzureManaged => AzureManaged.Tests}/Worker.AzureManaged.Tests.csproj (100%)
diff --git a/Microsoft.DurableTask.sln b/Microsoft.DurableTask.sln
index 45dae8aa..2b8bab8a 100644
--- a/Microsoft.DurableTask.sln
+++ b/Microsoft.DurableTask.sln
@@ -71,12 +71,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Analyzers.Tests", "test\Ana
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsApp.Tests", "samples\AzureFunctionsUnitTests\AzureFunctionsApp.Tests.csproj", "{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Tests", "test\Extensions\Azure\Azure.Tests.csproj", "{DBB5DB4E-A1B0-4C86-A233-213789C46929}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.AzureManaged", "src\Shared\AzureManaged\Shared.AzureManaged.csproj", "{82D06CA7-90B3-4791-9CAB-222F1AA113D5}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.AzureManaged", "test\Shared\AzureManaged\Shared.AzureManaged.csproj", "{F855ACBF-2A6A-4A43-A9E8-51B8599A539E}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -191,18 +185,6 @@ Global
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Release|Any CPU.Build.0 = Release|Any CPU
- {DBB5DB4E-A1B0-4C86-A233-213789C46929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DBB5DB4E-A1B0-4C86-A233-213789C46929}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DBB5DB4E-A1B0-4C86-A233-213789C46929}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DBB5DB4E-A1B0-4C86-A233-213789C46929}.Release|Any CPU.Build.0 = Release|Any CPU
- {82D06CA7-90B3-4791-9CAB-222F1AA113D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {82D06CA7-90B3-4791-9CAB-222F1AA113D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {82D06CA7-90B3-4791-9CAB-222F1AA113D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {82D06CA7-90B3-4791-9CAB-222F1AA113D5}.Release|Any CPU.Build.0 = Release|Any CPU
- {F855ACBF-2A6A-4A43-A9E8-51B8599A539E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F855ACBF-2A6A-4A43-A9E8-51B8599A539E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F855ACBF-2A6A-4A43-A9E8-51B8599A539E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F855ACBF-2A6A-4A43-A9E8-51B8599A539E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -238,9 +220,6 @@ Global
{998E9D97-BD36-4A9D-81FC-5DAC1CE40083} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
{541FCCCE-1059-4691-B027-F761CD80DE92} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
- {DBB5DB4E-A1B0-4C86-A233-213789C46929} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
- {82D06CA7-90B3-4791-9CAB-222F1AA113D5} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
- {F855ACBF-2A6A-4A43-A9E8-51B8599A539E} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
diff --git a/test/Client/AzureManaged/Client.AzureManaged.Tests.csproj b/test/Client/AzureManaged.Tests/Client.AzureManaged.Tests.csproj
similarity index 100%
rename from test/Client/AzureManaged/Client.AzureManaged.Tests.csproj
rename to test/Client/AzureManaged.Tests/Client.AzureManaged.Tests.csproj
diff --git a/test/Client/AzureManaged/DurableTaskSchedulerClientExtensionsTests.cs b/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs
similarity index 100%
rename from test/Client/AzureManaged/DurableTaskSchedulerClientExtensionsTests.cs
rename to test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs
diff --git a/test/Shared/AzureManaged/AccessTokenCacheTests.cs b/test/Shared/AzureManaged.Tests/AccessTokenCacheTests.cs
similarity index 100%
rename from test/Shared/AzureManaged/AccessTokenCacheTests.cs
rename to test/Shared/AzureManaged.Tests/AccessTokenCacheTests.cs
diff --git a/test/Shared/AzureManaged/DurableTaskSchedulerConnectionStringTests.cs b/test/Shared/AzureManaged.Tests/DurableTaskSchedulerConnectionStringTests.cs
similarity index 100%
rename from test/Shared/AzureManaged/DurableTaskSchedulerConnectionStringTests.cs
rename to test/Shared/AzureManaged.Tests/DurableTaskSchedulerConnectionStringTests.cs
diff --git a/test/Shared/AzureManaged/DurableTaskSchedulerOptionsTests.cs b/test/Shared/AzureManaged.Tests/DurableTaskSchedulerOptionsTests.cs
similarity index 100%
rename from test/Shared/AzureManaged/DurableTaskSchedulerOptionsTests.cs
rename to test/Shared/AzureManaged.Tests/DurableTaskSchedulerOptionsTests.cs
diff --git a/test/Shared/AzureManaged/Shared.AzureManaged.Tests.csproj b/test/Shared/AzureManaged.Tests/Shared.AzureManaged.Tests.csproj
similarity index 100%
rename from test/Shared/AzureManaged/Shared.AzureManaged.Tests.csproj
rename to test/Shared/AzureManaged.Tests/Shared.AzureManaged.Tests.csproj
diff --git a/test/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensionsTests.cs b/test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerExtensionsTests.cs
similarity index 100%
rename from test/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensionsTests.cs
rename to test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerExtensionsTests.cs
diff --git a/test/Worker/AzureManaged/Worker.AzureManaged.Tests.csproj b/test/Worker/AzureManaged.Tests/Worker.AzureManaged.Tests.csproj
similarity index 100%
rename from test/Worker/AzureManaged/Worker.AzureManaged.Tests.csproj
rename to test/Worker/AzureManaged.Tests/Worker.AzureManaged.Tests.csproj
From 77cd2d9b8b6ccd7548535762d8afdabd60a9cd85 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Sun, 12 Jan 2025 13:18:57 -0800
Subject: [PATCH 46/58] update sln
---
Microsoft.DurableTask.sln | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/Microsoft.DurableTask.sln b/Microsoft.DurableTask.sln
index 2b8bab8a..45bc8632 100644
--- a/Microsoft.DurableTask.sln
+++ b/Microsoft.DurableTask.sln
@@ -71,6 +71,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Analyzers.Tests", "test\Ana
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsApp.Tests", "samples\AzureFunctionsUnitTests\AzureFunctionsApp.Tests.csproj", "{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Worker.AzureManaged", "src\Worker\AzureManaged\Worker.AzureManaged.csproj", "{6106872F-A730-4A75-9267-1B2E2C2DC18C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.AzureManaged", "src\Client\AzureManaged\Client.AzureManaged.csproj", "{EAA6BE9B-E1A5-4E41-9511-EFEA24A51BA3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.AzureManaged.Tests", "test\Shared\AzureManaged.Tests\Shared.AzureManaged.Tests.csproj", "{11357B31-9A63-4A5A-9BC5-091952B25BC0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.AzureManaged.Tests", "test\Client\AzureManaged.Tests\Client.AzureManaged.Tests.csproj", "{A15BA625-DC6B-4C6D-8673-0CB08F1B9737}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Worker.AzureManaged.Tests", "test\Worker\AzureManaged.Tests\Worker.AzureManaged.Tests.csproj", "{B78F1FFD-47AC-45BE-8FF9-0BF8C9F35DEF}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -185,6 +195,26 @@ Global
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6106872F-A730-4A75-9267-1B2E2C2DC18C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6106872F-A730-4A75-9267-1B2E2C2DC18C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6106872F-A730-4A75-9267-1B2E2C2DC18C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6106872F-A730-4A75-9267-1B2E2C2DC18C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EAA6BE9B-E1A5-4E41-9511-EFEA24A51BA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EAA6BE9B-E1A5-4E41-9511-EFEA24A51BA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EAA6BE9B-E1A5-4E41-9511-EFEA24A51BA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EAA6BE9B-E1A5-4E41-9511-EFEA24A51BA3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {11357B31-9A63-4A5A-9BC5-091952B25BC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {11357B31-9A63-4A5A-9BC5-091952B25BC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {11357B31-9A63-4A5A-9BC5-091952B25BC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {11357B31-9A63-4A5A-9BC5-091952B25BC0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A15BA625-DC6B-4C6D-8673-0CB08F1B9737}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A15BA625-DC6B-4C6D-8673-0CB08F1B9737}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A15BA625-DC6B-4C6D-8673-0CB08F1B9737}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A15BA625-DC6B-4C6D-8673-0CB08F1B9737}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B78F1FFD-47AC-45BE-8FF9-0BF8C9F35DEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B78F1FFD-47AC-45BE-8FF9-0BF8C9F35DEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B78F1FFD-47AC-45BE-8FF9-0BF8C9F35DEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B78F1FFD-47AC-45BE-8FF9-0BF8C9F35DEF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -220,6 +250,11 @@ Global
{998E9D97-BD36-4A9D-81FC-5DAC1CE40083} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
{541FCCCE-1059-4691-B027-F761CD80DE92} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
+ {6106872F-A730-4A75-9267-1B2E2C2DC18C} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
+ {EAA6BE9B-E1A5-4E41-9511-EFEA24A51BA3} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
+ {11357B31-9A63-4A5A-9BC5-091952B25BC0} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
+ {A15BA625-DC6B-4C6D-8673-0CB08F1B9737} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
+ {B78F1FFD-47AC-45BE-8FF9-0BF8C9F35DEF} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
From 3af8b629f2628fa75890cf097bd5ce4c1948d6b9 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Fri, 10 Jan 2025 15:24:38 -0800
Subject: [PATCH 47/58] add sample
---
Microsoft.DurableTask.sln | 2 +
samples/AspNetWebApp/AspNetWebApp.csproj | 26 +++++++++
samples/AspNetWebApp/DockerFile | 22 ++++++++
.../Orchestrations/HelloCities.cs | 30 ++++++++++
samples/AspNetWebApp/Program.cs | 56 +++++++++++++++++++
.../Properties/launchSettings.json | 23 ++++++++
samples/AspNetWebApp/ScenariosController.cs | 50 +++++++++++++++++
samples/AspNetWebApp/Utils.cs | 38 +++++++++++++
.../AspNetWebApp/appsettings.Development.json | 8 +++
.../AspNetWebApp/appsettings.Production.json | 11 ++++
samples/AspNetWebApp/appsettings.json | 9 +++
.../DurableTaskSchedulerClientExtensions.cs | 2 +-
12 files changed, 276 insertions(+), 1 deletion(-)
create mode 100644 samples/AspNetWebApp/AspNetWebApp.csproj
create mode 100644 samples/AspNetWebApp/DockerFile
create mode 100644 samples/AspNetWebApp/Orchestrations/HelloCities.cs
create mode 100644 samples/AspNetWebApp/Program.cs
create mode 100644 samples/AspNetWebApp/Properties/launchSettings.json
create mode 100644 samples/AspNetWebApp/ScenariosController.cs
create mode 100644 samples/AspNetWebApp/Utils.cs
create mode 100644 samples/AspNetWebApp/appsettings.Development.json
create mode 100644 samples/AspNetWebApp/appsettings.Production.json
create mode 100644 samples/AspNetWebApp/appsettings.json
diff --git a/Microsoft.DurableTask.sln b/Microsoft.DurableTask.sln
index 45bc8632..d09e0550 100644
--- a/Microsoft.DurableTask.sln
+++ b/Microsoft.DurableTask.sln
@@ -81,6 +81,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.AzureManaged.Tests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Worker.AzureManaged.Tests", "test\Worker\AzureManaged.Tests\Worker.AzureManaged.Tests.csproj", "{B78F1FFD-47AC-45BE-8FF9-0BF8C9F35DEF}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetWebApp", "samples\portable-sdk\dotnet\AspNetWebApp\AspNetWebApp.csproj", "{869D2D51-9372-4764-B059-C43B6C1180A3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/samples/AspNetWebApp/AspNetWebApp.csproj b/samples/AspNetWebApp/AspNetWebApp.csproj
new file mode 100644
index 00000000..8a88103b
--- /dev/null
+++ b/samples/AspNetWebApp/AspNetWebApp.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net8.0
+ enable
+ enable
+ true
+ $(BaseIntermediateOutputPath)Generated
+
+
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/AspNetWebApp/DockerFile b/samples/AspNetWebApp/DockerFile
new file mode 100644
index 00000000..5ddb3ae9
--- /dev/null
+++ b/samples/AspNetWebApp/DockerFile
@@ -0,0 +1,22 @@
+#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
+WORKDIR /app
+EXPOSE 8080
+
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+WORKDIR /src
+COPY ["AspNetWebApp.csproj", "."]
+RUN dotnet restore "./AspNetWebApp.csproj"
+COPY . .
+WORKDIR "/src/."
+RUN dotnet build "AspNetWebApp.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "AspNetWebApp.csproj" -c Release -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENV ASPNETCORE_ENVIRONMENT=Production
+ENTRYPOINT ["dotnet", "AspNetWebApp.dll"]
diff --git a/samples/AspNetWebApp/Orchestrations/HelloCities.cs b/samples/AspNetWebApp/Orchestrations/HelloCities.cs
new file mode 100644
index 00000000..5c48dcaa
--- /dev/null
+++ b/samples/AspNetWebApp/Orchestrations/HelloCities.cs
@@ -0,0 +1,30 @@
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Client;
+
+namespace AspNetWebApp.Scenarios;
+
+[DurableTask]
+class HelloCities : TaskOrchestrator>
+{
+ public override async Task> RunAsync(TaskOrchestrationContext context, string input)
+ {
+ List results =
+ [
+ await context.CallSayHelloAsync("Seattle"),
+ await context.CallSayHelloAsync("Amsterdam"),
+ await context.CallSayHelloAsync("Hyderabad"),
+ await context.CallSayHelloAsync("Shanghai"),
+ await context.CallSayHelloAsync("Tokyo"),
+ ];
+ return results;
+ }
+}
+
+[DurableTask]
+class SayHello : TaskActivity
+{
+ public override Task RunAsync(TaskActivityContext context, string cityName)
+ {
+ return Task.FromResult($"Hello, {cityName}!");
+ }
+}
diff --git a/samples/AspNetWebApp/Program.cs b/samples/AspNetWebApp/Program.cs
new file mode 100644
index 00000000..1887c4db
--- /dev/null
+++ b/samples/AspNetWebApp/Program.cs
@@ -0,0 +1,56 @@
+using System.Text.Json.Serialization;
+using Azure.Core;
+using Azure.Identity;
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Client;
+using Microsoft.DurableTask.Worker;
+using Microsoft.DurableTask.Worker.AzureManaged;
+using Microsoft.DurableTask.Client.AzureManaged;
+
+WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+
+string endpointAddress = builder.Configuration["DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS"]
+ ?? throw new InvalidOperationException("Missing required configuration 'DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS'");
+
+string taskHubName = builder.Configuration["DURABLE_TASK_SCHEDULER_TASK_HUB_NAME"]
+ ?? throw new InvalidOperationException("Missing required configuration 'DURABLE_TASK_SCHEDULER_TASK_HUB_NAME'");
+
+TokenCredential credential = builder.Environment.IsProduction()
+ ? new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = builder.Configuration["CONTAINER_APP_UMI_CLIENT_ID"] })
+ : new DefaultAzureCredential();
+
+// Add all the generated orchestrations and activities automatically
+builder.Services.AddDurableTaskWorker(builder =>
+{
+ builder.AddTasks(r => r.AddAllGeneratedTasks());
+ builder.UseDurableTaskScheduler(endpointAddress, taskHubName, credential);
+});
+
+// Register the client, which can be used to start orchestrations
+builder.Services.AddDurableTaskClient(builder =>
+{
+ builder.UseDurableTaskScheduler(endpointAddress, taskHubName, credential);
+});
+
+// Configure console logging using the simpler, more compact format
+builder.Services.AddLogging(logging =>
+{
+ logging.AddSimpleConsole(options =>
+ {
+ options.SingleLine = true;
+ options.UseUtcTimestamp = true;
+ options.TimestampFormat = "yyyy-MM-ddTHH:mm:ss.fffZ ";
+ });
+});
+
+// Configure the HTTP request pipeline
+builder.Services.AddControllers().AddJsonOptions(options =>
+{
+ options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
+ options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
+});
+
+// The actual listen URL can be configured in environment variables named "ASPNETCORE_URLS" or "ASPNETCORE_URLS_HTTPS"
+WebApplication app = builder.Build();
+app.MapControllers();
+app.Run();
diff --git a/samples/AspNetWebApp/Properties/launchSettings.json b/samples/AspNetWebApp/Properties/launchSettings.json
new file mode 100644
index 00000000..9e08b95b
--- /dev/null
+++ b/samples/AspNetWebApp/Properties/launchSettings.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:36209",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://localhost:5008",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS": "https://wbtestdts02-g7ahczeycua9.westus2.durabletask.io",
+ "DURABLE_TASK_SCHEDULER_TASK_HUB_NAME": "wbtb100"
+ }
+ }
+ }
+}
diff --git a/samples/AspNetWebApp/ScenariosController.cs b/samples/AspNetWebApp/ScenariosController.cs
new file mode 100644
index 00000000..d6049129
--- /dev/null
+++ b/samples/AspNetWebApp/ScenariosController.cs
@@ -0,0 +1,50 @@
+using System.Diagnostics;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Client;
+
+namespace AspNetWebApp;
+
+[Route("scenarios")]
+[ApiController]
+public partial class ScenariosController(
+ DurableTaskClient durableTaskClient,
+ ILogger logger) : ControllerBase
+{
+ readonly DurableTaskClient durableTaskClient = durableTaskClient;
+ readonly ILogger logger = logger;
+
+ [HttpPost("hellocities")]
+ public async Task RunHelloCities([FromQuery] int? count, [FromQuery] string? prefix)
+ {
+ if (count is null || count < 1)
+ {
+ return this.BadRequest(new { error = "A 'count' query string parameter is required and it must contain a positive number." });
+ }
+
+ // Generate a semi-unique prefix for the instance IDs to simplify tracking
+ prefix ??= $"hellocities-{count}-";
+ prefix += DateTime.UtcNow.ToString("yyyyMMdd-hhmmss");
+
+ this.logger.LogInformation("Scheduling {count} orchestrations with a prefix of '{prefix}'...", count, prefix);
+
+ Stopwatch sw = Stopwatch.StartNew();
+ await Enumerable.Range(0, count.Value).ParallelForEachAsync(1000, i =>
+ {
+ string instanceId = $"{prefix}-{i:X16}";
+ return this.durableTaskClient.ScheduleNewHelloCitiesInstanceAsync(
+ input: null!,
+ new StartOrchestrationOptions(instanceId));
+ });
+
+ sw.Stop();
+ this.logger.LogInformation(
+ "All {count} orchestrations were scheduled successfully in {time}ms!",
+ count,
+ sw.ElapsedMilliseconds);
+ return this.Ok(new
+ {
+ message = $"Scheduled {count} orchestrations prefixed with '{prefix}' in {sw.ElapsedMilliseconds}."
+ });
+ }
+}
diff --git a/samples/AspNetWebApp/Utils.cs b/samples/AspNetWebApp/Utils.cs
new file mode 100644
index 00000000..6ddabf39
--- /dev/null
+++ b/samples/AspNetWebApp/Utils.cs
@@ -0,0 +1,38 @@
+namespace AspNetWebApp;
+
+static class Utils
+{
+ public static async Task ParallelForEachAsync(this IEnumerable items, int maxConcurrency, Func action)
+ {
+ List tasks;
+ if (items is ICollection itemCollection)
+ {
+ tasks = new List(itemCollection.Count);
+ }
+ else
+ {
+ tasks = [];
+ }
+
+ using SemaphoreSlim semaphore = new(maxConcurrency);
+ foreach (T item in items)
+ {
+ tasks.Add(InvokeThrottledAction(item, action, semaphore));
+ }
+
+ await Task.WhenAll(tasks);
+ }
+
+ static async Task InvokeThrottledAction(T item, Func action, SemaphoreSlim semaphore)
+ {
+ await semaphore.WaitAsync();
+ try
+ {
+ await action(item);
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ }
+}
diff --git a/samples/AspNetWebApp/appsettings.Development.json b/samples/AspNetWebApp/appsettings.Development.json
new file mode 100644
index 00000000..a6e86ace
--- /dev/null
+++ b/samples/AspNetWebApp/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Debug",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/samples/AspNetWebApp/appsettings.Production.json b/samples/AspNetWebApp/appsettings.Production.json
new file mode 100644
index 00000000..70b7d1d9
--- /dev/null
+++ b/samples/AspNetWebApp/appsettings.Production.json
@@ -0,0 +1,11 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS": "https://{your-durable-task-endpoint}.durabletask.io",
+ "DURABLE_TASK_SCHEDULER_TASK_HUB_NAME": "{your-task-hub-name}",
+ "CONTAINER_APP_UMI_CLIENT_ID": "{your-user-managed-identity-client-id}"
+}
diff --git a/samples/AspNetWebApp/appsettings.json b/samples/AspNetWebApp/appsettings.json
new file mode 100644
index 00000000..10f68b8c
--- /dev/null
+++ b/samples/AspNetWebApp/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
index 268617a0..7c3c4c34 100644
--- a/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
+++ b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
@@ -7,7 +7,7 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
-namespace Microsoft.DurableTask.Client;
+namespace Microsoft.DurableTask.Client.AzureManaged;
///
/// Extension methods for configuring Durable Task clients to use the Azure Durable Task Scheduler service.
From 549e105f77e76b6b9bac519ad338042bbb32b121 Mon Sep 17 00:00:00 2001
From: wangbill
Date: Mon, 13 Jan 2025 14:58:05 -0800
Subject: [PATCH 48/58] Update
src/Shared/AzureManaged/Shared.AzureManaged.csproj
Co-authored-by: Jacob Viau
---
src/Shared/AzureManaged/Shared.AzureManaged.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Shared/AzureManaged/Shared.AzureManaged.csproj b/src/Shared/AzureManaged/Shared.AzureManaged.csproj
index 2ee55cde..d4bfb201 100644
--- a/src/Shared/AzureManaged/Shared.AzureManaged.csproj
+++ b/src/Shared/AzureManaged/Shared.AzureManaged.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net6.0
Shared components for Azure Managed extensions for the Durable Task Framework.
true
From 3a40d0977eb8ff4963bfa676f013e5de230140bf Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Mon, 13 Jan 2025 15:36:57 -0800
Subject: [PATCH 49/58] fix compile
---
Microsoft.DurableTask.sln | 10 +-
samples/AspNetWebApp/AspNetWebApp.csproj | 26 ---
samples/AspNetWebApp/DockerFile | 22 --
.../Orchestrations/HelloCities.cs | 30 ---
samples/AspNetWebApp/Program.cs | 56 -----
.../Properties/launchSettings.json | 23 --
samples/AspNetWebApp/ScenariosController.cs | 50 -----
samples/AspNetWebApp/Utils.cs | 38 ----
.../AspNetWebApp/appsettings.Development.json | 8 -
.../AspNetWebApp/appsettings.Production.json | 11 -
samples/AspNetWebApp/appsettings.json | 9 -
.../AzureManaged/Client.AzureManaged.csproj | 12 +-
.../DurableTaskSchedulerClientExtensions.cs | 18 +-
.../DurableTaskSchedulerClientOptions.cs | 166 ++++++++++++++
.../DurableTaskSchedulerConnectionString.cs | 2 +-
.../AzureManaged/Shared.AzureManaged.csproj | 25 ---
.../DurableTaskSchedulerWorkerExtensions.cs | 14 +-
.../DurableTaskSchedulerWorkerOptions.cs} | 8 +-
.../AzureManaged/Worker.AzureManaged.csproj | 12 +-
.../Client.AzureManaged.Tests.csproj | 8 +-
...rableTaskSchedulerClientExtensionsTests.cs | 8 +-
.../DurableTaskSchedulerClientOptionsTests.cs | 211 ++++++++++++++++++
.../AccessTokenCacheTests.cs | 5 +-
.../Shared.AzureManaged.Tests.csproj | 10 +-
...rableTaskSchedulerWorkerExtensionsTests.cs | 8 +-
...DurableTaskSchedulerWorkerOptionsTests.cs} | 28 +--
.../Worker.AzureManaged.Tests.csproj | 4 +-
27 files changed, 470 insertions(+), 352 deletions(-)
delete mode 100644 samples/AspNetWebApp/AspNetWebApp.csproj
delete mode 100644 samples/AspNetWebApp/DockerFile
delete mode 100644 samples/AspNetWebApp/Orchestrations/HelloCities.cs
delete mode 100644 samples/AspNetWebApp/Program.cs
delete mode 100644 samples/AspNetWebApp/Properties/launchSettings.json
delete mode 100644 samples/AspNetWebApp/ScenariosController.cs
delete mode 100644 samples/AspNetWebApp/Utils.cs
delete mode 100644 samples/AspNetWebApp/appsettings.Development.json
delete mode 100644 samples/AspNetWebApp/appsettings.Production.json
delete mode 100644 samples/AspNetWebApp/appsettings.json
create mode 100644 src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs
delete mode 100644 src/Shared/AzureManaged/Shared.AzureManaged.csproj
rename src/{Shared/AzureManaged/DurableTaskSchedulerOptions.cs => Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs} (96%)
create mode 100644 test/Client/AzureManaged.Tests/DurableTaskSchedulerClientOptionsTests.cs
rename test/{Shared/AzureManaged.Tests/DurableTaskSchedulerOptionsTests.cs => Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerOptionsTests.cs} (84%)
diff --git a/Microsoft.DurableTask.sln b/Microsoft.DurableTask.sln
index d09e0550..a3a2af17 100644
--- a/Microsoft.DurableTask.sln
+++ b/Microsoft.DurableTask.sln
@@ -81,8 +81,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.AzureManaged.Tests",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Worker.AzureManaged.Tests", "test\Worker\AzureManaged.Tests\Worker.AzureManaged.Tests.csproj", "{B78F1FFD-47AC-45BE-8FF9-0BF8C9F35DEF}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetWebApp", "samples\portable-sdk\dotnet\AspNetWebApp\AspNetWebApp.csproj", "{869D2D51-9372-4764-B059-C43B6C1180A3}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -217,6 +215,14 @@ Global
{B78F1FFD-47AC-45BE-8FF9-0BF8C9F35DEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B78F1FFD-47AC-45BE-8FF9-0BF8C9F35DEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B78F1FFD-47AC-45BE-8FF9-0BF8C9F35DEF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {869D2D51-9372-4764-B059-C43B6C1180A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {869D2D51-9372-4764-B059-C43B6C1180A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {869D2D51-9372-4764-B059-C43B6C1180A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {869D2D51-9372-4764-B059-C43B6C1180A3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D4C87C0F-66CD-459D-B271-340C6D180448}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D4C87C0F-66CD-459D-B271-340C6D180448}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D4C87C0F-66CD-459D-B271-340C6D180448}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D4C87C0F-66CD-459D-B271-340C6D180448}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/samples/AspNetWebApp/AspNetWebApp.csproj b/samples/AspNetWebApp/AspNetWebApp.csproj
deleted file mode 100644
index 8a88103b..00000000
--- a/samples/AspNetWebApp/AspNetWebApp.csproj
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
- net8.0
- enable
- enable
- true
- $(BaseIntermediateOutputPath)Generated
-
-
- false
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/samples/AspNetWebApp/DockerFile b/samples/AspNetWebApp/DockerFile
deleted file mode 100644
index 5ddb3ae9..00000000
--- a/samples/AspNetWebApp/DockerFile
+++ /dev/null
@@ -1,22 +0,0 @@
-#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
-
-FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
-WORKDIR /app
-EXPOSE 8080
-
-FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
-WORKDIR /src
-COPY ["AspNetWebApp.csproj", "."]
-RUN dotnet restore "./AspNetWebApp.csproj"
-COPY . .
-WORKDIR "/src/."
-RUN dotnet build "AspNetWebApp.csproj" -c Release -o /app/build
-
-FROM build AS publish
-RUN dotnet publish "AspNetWebApp.csproj" -c Release -o /app/publish /p:UseAppHost=false
-
-FROM base AS final
-WORKDIR /app
-COPY --from=publish /app/publish .
-ENV ASPNETCORE_ENVIRONMENT=Production
-ENTRYPOINT ["dotnet", "AspNetWebApp.dll"]
diff --git a/samples/AspNetWebApp/Orchestrations/HelloCities.cs b/samples/AspNetWebApp/Orchestrations/HelloCities.cs
deleted file mode 100644
index 5c48dcaa..00000000
--- a/samples/AspNetWebApp/Orchestrations/HelloCities.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Microsoft.DurableTask;
-using Microsoft.DurableTask.Client;
-
-namespace AspNetWebApp.Scenarios;
-
-[DurableTask]
-class HelloCities : TaskOrchestrator>
-{
- public override async Task> RunAsync(TaskOrchestrationContext context, string input)
- {
- List results =
- [
- await context.CallSayHelloAsync("Seattle"),
- await context.CallSayHelloAsync("Amsterdam"),
- await context.CallSayHelloAsync("Hyderabad"),
- await context.CallSayHelloAsync("Shanghai"),
- await context.CallSayHelloAsync("Tokyo"),
- ];
- return results;
- }
-}
-
-[DurableTask]
-class SayHello : TaskActivity
-{
- public override Task RunAsync(TaskActivityContext context, string cityName)
- {
- return Task.FromResult($"Hello, {cityName}!");
- }
-}
diff --git a/samples/AspNetWebApp/Program.cs b/samples/AspNetWebApp/Program.cs
deleted file mode 100644
index 1887c4db..00000000
--- a/samples/AspNetWebApp/Program.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System.Text.Json.Serialization;
-using Azure.Core;
-using Azure.Identity;
-using Microsoft.DurableTask;
-using Microsoft.DurableTask.Client;
-using Microsoft.DurableTask.Worker;
-using Microsoft.DurableTask.Worker.AzureManaged;
-using Microsoft.DurableTask.Client.AzureManaged;
-
-WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
-
-string endpointAddress = builder.Configuration["DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS"]
- ?? throw new InvalidOperationException("Missing required configuration 'DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS'");
-
-string taskHubName = builder.Configuration["DURABLE_TASK_SCHEDULER_TASK_HUB_NAME"]
- ?? throw new InvalidOperationException("Missing required configuration 'DURABLE_TASK_SCHEDULER_TASK_HUB_NAME'");
-
-TokenCredential credential = builder.Environment.IsProduction()
- ? new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = builder.Configuration["CONTAINER_APP_UMI_CLIENT_ID"] })
- : new DefaultAzureCredential();
-
-// Add all the generated orchestrations and activities automatically
-builder.Services.AddDurableTaskWorker(builder =>
-{
- builder.AddTasks(r => r.AddAllGeneratedTasks());
- builder.UseDurableTaskScheduler(endpointAddress, taskHubName, credential);
-});
-
-// Register the client, which can be used to start orchestrations
-builder.Services.AddDurableTaskClient(builder =>
-{
- builder.UseDurableTaskScheduler(endpointAddress, taskHubName, credential);
-});
-
-// Configure console logging using the simpler, more compact format
-builder.Services.AddLogging(logging =>
-{
- logging.AddSimpleConsole(options =>
- {
- options.SingleLine = true;
- options.UseUtcTimestamp = true;
- options.TimestampFormat = "yyyy-MM-ddTHH:mm:ss.fffZ ";
- });
-});
-
-// Configure the HTTP request pipeline
-builder.Services.AddControllers().AddJsonOptions(options =>
-{
- options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
- options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
-});
-
-// The actual listen URL can be configured in environment variables named "ASPNETCORE_URLS" or "ASPNETCORE_URLS_HTTPS"
-WebApplication app = builder.Build();
-app.MapControllers();
-app.Run();
diff --git a/samples/AspNetWebApp/Properties/launchSettings.json b/samples/AspNetWebApp/Properties/launchSettings.json
deleted file mode 100644
index 9e08b95b..00000000
--- a/samples/AspNetWebApp/Properties/launchSettings.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "$schema": "http://json.schemastore.org/launchsettings.json",
- "iisSettings": {
- "windowsAuthentication": false,
- "anonymousAuthentication": true,
- "iisExpress": {
- "applicationUrl": "http://localhost:36209",
- "sslPort": 0
- }
- },
- "profiles": {
- "http": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "applicationUrl": "http://localhost:5008",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development",
- "DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS": "https://wbtestdts02-g7ahczeycua9.westus2.durabletask.io",
- "DURABLE_TASK_SCHEDULER_TASK_HUB_NAME": "wbtb100"
- }
- }
- }
-}
diff --git a/samples/AspNetWebApp/ScenariosController.cs b/samples/AspNetWebApp/ScenariosController.cs
deleted file mode 100644
index d6049129..00000000
--- a/samples/AspNetWebApp/ScenariosController.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System.Diagnostics;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.DurableTask;
-using Microsoft.DurableTask.Client;
-
-namespace AspNetWebApp;
-
-[Route("scenarios")]
-[ApiController]
-public partial class ScenariosController(
- DurableTaskClient durableTaskClient,
- ILogger logger) : ControllerBase
-{
- readonly DurableTaskClient durableTaskClient = durableTaskClient;
- readonly ILogger logger = logger;
-
- [HttpPost("hellocities")]
- public async Task RunHelloCities([FromQuery] int? count, [FromQuery] string? prefix)
- {
- if (count is null || count < 1)
- {
- return this.BadRequest(new { error = "A 'count' query string parameter is required and it must contain a positive number." });
- }
-
- // Generate a semi-unique prefix for the instance IDs to simplify tracking
- prefix ??= $"hellocities-{count}-";
- prefix += DateTime.UtcNow.ToString("yyyyMMdd-hhmmss");
-
- this.logger.LogInformation("Scheduling {count} orchestrations with a prefix of '{prefix}'...", count, prefix);
-
- Stopwatch sw = Stopwatch.StartNew();
- await Enumerable.Range(0, count.Value).ParallelForEachAsync(1000, i =>
- {
- string instanceId = $"{prefix}-{i:X16}";
- return this.durableTaskClient.ScheduleNewHelloCitiesInstanceAsync(
- input: null!,
- new StartOrchestrationOptions(instanceId));
- });
-
- sw.Stop();
- this.logger.LogInformation(
- "All {count} orchestrations were scheduled successfully in {time}ms!",
- count,
- sw.ElapsedMilliseconds);
- return this.Ok(new
- {
- message = $"Scheduled {count} orchestrations prefixed with '{prefix}' in {sw.ElapsedMilliseconds}."
- });
- }
-}
diff --git a/samples/AspNetWebApp/Utils.cs b/samples/AspNetWebApp/Utils.cs
deleted file mode 100644
index 6ddabf39..00000000
--- a/samples/AspNetWebApp/Utils.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-namespace AspNetWebApp;
-
-static class Utils
-{
- public static async Task ParallelForEachAsync(this IEnumerable items, int maxConcurrency, Func action)
- {
- List tasks;
- if (items is ICollection itemCollection)
- {
- tasks = new List(itemCollection.Count);
- }
- else
- {
- tasks = [];
- }
-
- using SemaphoreSlim semaphore = new(maxConcurrency);
- foreach (T item in items)
- {
- tasks.Add(InvokeThrottledAction(item, action, semaphore));
- }
-
- await Task.WhenAll(tasks);
- }
-
- static async Task InvokeThrottledAction(T item, Func action, SemaphoreSlim semaphore)
- {
- await semaphore.WaitAsync();
- try
- {
- await action(item);
- }
- finally
- {
- semaphore.Release();
- }
- }
-}
diff --git a/samples/AspNetWebApp/appsettings.Development.json b/samples/AspNetWebApp/appsettings.Development.json
deleted file mode 100644
index a6e86ace..00000000
--- a/samples/AspNetWebApp/appsettings.Development.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Debug",
- "Microsoft.AspNetCore": "Warning"
- }
- }
-}
diff --git a/samples/AspNetWebApp/appsettings.Production.json b/samples/AspNetWebApp/appsettings.Production.json
deleted file mode 100644
index 70b7d1d9..00000000
--- a/samples/AspNetWebApp/appsettings.Production.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
- },
- "DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS": "https://{your-durable-task-endpoint}.durabletask.io",
- "DURABLE_TASK_SCHEDULER_TASK_HUB_NAME": "{your-task-hub-name}",
- "CONTAINER_APP_UMI_CLIENT_ID": "{your-user-managed-identity-client-id}"
-}
diff --git a/samples/AspNetWebApp/appsettings.json b/samples/AspNetWebApp/appsettings.json
deleted file mode 100644
index 10f68b8c..00000000
--- a/samples/AspNetWebApp/appsettings.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft.AspNetCore": "Warning"
- }
- },
- "AllowedHosts": "*"
-}
diff --git a/src/Client/AzureManaged/Client.AzureManaged.csproj b/src/Client/AzureManaged/Client.AzureManaged.csproj
index 1cf0934c..29f0dfd8 100644
--- a/src/Client/AzureManaged/Client.AzureManaged.csproj
+++ b/src/Client/AzureManaged/Client.AzureManaged.csproj
@@ -8,7 +8,17 @@
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
index 7c3c4c34..a5432bf8 100644
--- a/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
+++ b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
@@ -21,15 +21,15 @@ public static class DurableTaskSchedulerClientExtensions
/// The endpoint address of the Durable Task Scheduler resource. Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".
/// The name of the task hub resource associated with the Durable Task Scheduler resource.
/// The credential used to authenticate with the Durable Task Scheduler task hub resource.
- /// Optional callback to dynamically configure DurableTaskSchedulerOptions.
+ /// Optional callback to dynamically configure DurableTaskSchedulerClientOptions.
public static void UseDurableTaskScheduler(
this IDurableTaskClientBuilder builder,
string endpointAddress,
string taskHubName,
TokenCredential credential,
- Action? configure = null)
+ Action? configure = null)
{
- builder.Services.AddOptions(builder.Name)
+ builder.Services.AddOptions(builder.Name)
.Configure(options =>
{
options.EndpointAddress = endpointAddress;
@@ -50,15 +50,15 @@ public static void UseDurableTaskScheduler(
///
/// The Durable Task client builder to configure.
/// The connection string used to connect to the Durable Task Scheduler service.
- /// Optional callback to dynamically configure DurableTaskSchedulerOptions.
+ /// Optional callback to dynamically configure DurableTaskSchedulerClientOptions.
public static void UseDurableTaskScheduler(
this IDurableTaskClientBuilder builder,
string connectionString,
- Action? configure = null)
+ Action? configure = null)
{
- var connectionOptions = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var connectionOptions = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
- builder.Services.AddOptions(builder.Name)
+ builder.Services.AddOptions(builder.Name)
.Configure(options =>
{
options.EndpointAddress = connectionOptions.EndpointAddress;
@@ -79,7 +79,7 @@ public static void UseDurableTaskScheduler(
/// using the provided Durable Task Scheduler options.
///
/// Monitor for accessing the current scheduler options configuration.
- internal class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
+ internal class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
IConfigureNamedOptions
{
///
@@ -95,7 +95,7 @@ internal class ConfigureGrpcChannel(IOptionsMonitor
/// The options instance to configure.
public void Configure(string? name, GrpcDurableTaskClientOptions options)
{
- DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
+ DurableTaskSchedulerClientOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
options.Channel = source.CreateChannel();
}
}
diff --git a/src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs b/src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs
new file mode 100644
index 00000000..30ab86a3
--- /dev/null
+++ b/src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs
@@ -0,0 +1,166 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using Azure.Core;
+using Azure.Identity;
+using Grpc.Core;
+using Grpc.Net.Client;
+
+namespace Microsoft.DurableTask;
+
+///
+/// Options for configuring the Durable Task Scheduler.
+///
+public class DurableTaskSchedulerClientOptions
+{
+ ///
+ /// Gets or sets the endpoint address of the Durable Task Scheduler resource.
+ /// Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".
+ ///
+ [Required(ErrorMessage = "Endpoint address is required")]
+ public string EndpointAddress { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the name of the task hub resource associated with the Durable Task Scheduler resource.
+ ///
+ [Required(ErrorMessage = "Task hub name is required")]
+ public string TaskHubName { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the credential used to authenticate with the Durable Task Scheduler task hub resource.
+ ///
+ public TokenCredential? Credential { get; set; }
+
+ ///
+ /// Gets or sets the resource ID of the Durable Task Scheduler resource.
+ /// The default value is https://durabletask.io.
+ ///
+ public string ResourceId { get; set; } = "https://durabletask.io";
+
+ ///
+ /// Gets or sets the worker ID used to identify the worker instance.
+ /// The default value is a string containing the machine name, process ID, and a unique identifier.
+ ///
+ public string WorkerId { get; set; } = $"{Environment.MachineName},{Environment.ProcessId},{Guid.NewGuid():N}";
+
+ ///
+ /// Gets or sets a value indicating whether to allow insecure channel credentials.
+ /// This should only be set to true in development/testing scenarios.
+ ///
+ public bool AllowInsecureCredentials { get; set; }
+
+ ///
+ /// Creates a new instance of from a connection string.
+ ///
+ /// The connection string to parse.
+ /// A new instance of .
+ public static DurableTaskSchedulerClientOptions FromConnectionString(string connectionString)
+ {
+ return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
+ }
+
+ ///
+ /// Creates a new instance of from a parsed connection string.
+ ///
+ /// The connection string to parse.
+ /// A new instance of .
+ internal static DurableTaskSchedulerClientOptions FromConnectionString(
+ DurableTaskSchedulerConnectionString connectionString) => new()
+ {
+ EndpointAddress = connectionString.Endpoint,
+ TaskHubName = connectionString.TaskHubName,
+ Credential = GetCredentialFromConnectionString(connectionString),
+ };
+
+ ///
+ /// Creates a gRPC channel for communicating with the Durable Task Scheduler service.
+ ///
+ /// A configured instance that can be used to make gRPC calls.
+ public GrpcChannel CreateChannel()
+ {
+ Verify.NotNull(this.EndpointAddress, nameof(this.EndpointAddress));
+ Verify.NotNull(this.TaskHubName, nameof(this.TaskHubName));
+ string taskHubName = this.TaskHubName;
+ string endpoint = !this.EndpointAddress.Contains("://")
+ ? $"https://{this.EndpointAddress}"
+ : this.EndpointAddress;
+ AccessTokenCache? cache =
+ this.Credential is not null
+ ? new AccessTokenCache(
+ this.Credential,
+ new TokenRequestContext(new[] { $"{this.ResourceId}/.default" }),
+ TimeSpan.FromMinutes(5))
+ : null;
+ CallCredentials managedBackendCreds = CallCredentials.FromInterceptor(
+ async (context, metadata) =>
+ {
+ metadata.Add("taskhub", taskHubName);
+ metadata.Add("workerid", this.WorkerId);
+ if (cache == null)
+ {
+ return;
+ }
+
+ AccessToken token = await cache.GetTokenAsync(context.CancellationToken);
+ metadata.Add("Authorization", $"Bearer {token.Token}");
+ });
+
+ // Production will use HTTPS, but local testing will use HTTP
+ ChannelCredentials channelCreds = endpoint.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ?
+ ChannelCredentials.SecureSsl :
+ ChannelCredentials.Insecure;
+ return GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
+ {
+ Credentials = ChannelCredentials.Create(channelCreds, managedBackendCreds),
+ UnsafeUseInsecureChannelCallCredentials = this.AllowInsecureCredentials,
+ });
+ }
+
+ static TokenCredential? GetCredentialFromConnectionString(DurableTaskSchedulerConnectionString connectionString)
+ {
+ string authType = connectionString.Authentication;
+
+ // Parse the supported auth types, in a case-insensitive way and without spaces
+ switch (authType.ToLower(CultureInfo.InvariantCulture).Replace(" ", string.Empty))
+ {
+ case "defaultazure":
+ return new DefaultAzureCredential();
+ case "managedidentity":
+ return new ManagedIdentityCredential(connectionString.ClientId);
+ case "workloadidentity":
+ var opts = new WorkloadIdentityCredentialOptions();
+ if (!string.IsNullOrEmpty(connectionString.ClientId))
+ {
+ opts.ClientId = connectionString.ClientId;
+ }
+
+ if (!string.IsNullOrEmpty(connectionString.TenantId))
+ {
+ opts.TenantId = connectionString.TenantId;
+ }
+
+ if (connectionString.AdditionallyAllowedTenants is not null)
+ {
+ foreach (string tenant in connectionString.AdditionallyAllowedTenants)
+ {
+ opts.AdditionallyAllowedTenants.Add(tenant);
+ }
+ }
+
+ return new WorkloadIdentityCredential(opts);
+ case "environment":
+ return new EnvironmentCredential();
+ case "azurecli":
+ return new AzureCliCredential();
+ case "azurepowershell":
+ return new AzurePowerShellCredential();
+ case "none":
+ return null;
+ default:
+ throw new ArgumentException(
+ $"The connection string contains an unsupported authentication type '{authType}'.",
+ nameof(connectionString));
+ }
+ }
+}
diff --git a/src/Shared/AzureManaged/DurableTaskSchedulerConnectionString.cs b/src/Shared/AzureManaged/DurableTaskSchedulerConnectionString.cs
index 7d3d18b4..40dc470c 100644
--- a/src/Shared/AzureManaged/DurableTaskSchedulerConnectionString.cs
+++ b/src/Shared/AzureManaged/DurableTaskSchedulerConnectionString.cs
@@ -7,7 +7,7 @@ namespace Microsoft.DurableTask;
///
/// Represents the constituent parts of a connection string for a Durable Task Scheduler service.
///
-public sealed class DurableTaskSchedulerConnectionString
+sealed class DurableTaskSchedulerConnectionString
{
readonly DbConnectionStringBuilder builder;
diff --git a/src/Shared/AzureManaged/Shared.AzureManaged.csproj b/src/Shared/AzureManaged/Shared.AzureManaged.csproj
deleted file mode 100644
index d4bfb201..00000000
--- a/src/Shared/AzureManaged/Shared.AzureManaged.csproj
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
- net6.0
- Shared components for Azure Managed extensions for the Durable Task Framework.
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
index b8dff6b4..300e1492 100644
--- a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
+++ b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
@@ -28,9 +28,9 @@ public static void UseDurableTaskScheduler(
string endpointAddress,
string taskHubName,
TokenCredential credential,
- Action? configure = null)
+ Action? configure = null)
{
- builder.Services.AddOptions(builder.Name)
+ builder.Services.AddOptions(builder.Name)
.Configure(options =>
{
options.EndpointAddress = endpointAddress;
@@ -55,11 +55,11 @@ public static void UseDurableTaskScheduler(
public static void UseDurableTaskScheduler(
this IDurableTaskWorkerBuilder builder,
string connectionString,
- Action? configure = null)
+ Action? configure = null)
{
- var connectionOptions = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var connectionOptions = DurableTaskSchedulerWorkerOptions.FromConnectionString(connectionString);
- builder.Services.AddOptions(builder.Name)
+ builder.Services.AddOptions(builder.Name)
.Configure(options =>
{
options.EndpointAddress = connectionOptions.EndpointAddress;
@@ -80,7 +80,7 @@ public static void UseDurableTaskScheduler(
/// using the provided Durable Task Scheduler options.
///
/// Monitor for accessing the current scheduler options configuration.
- internal class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
+ internal class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
IConfigureNamedOptions
{
///
@@ -96,7 +96,7 @@ internal class ConfigureGrpcChannel(IOptionsMonitor
/// The options instance to configure.
public void Configure(string? name, GrpcDurableTaskWorkerOptions options)
{
- DurableTaskSchedulerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
+ DurableTaskSchedulerWorkerOptions source = schedulerOptions.Get(name ?? Options.DefaultName);
options.Channel = source.CreateChannel();
}
}
diff --git a/src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs
similarity index 96%
rename from src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs
rename to src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs
index 5a9ce98b..a7f79510 100644
--- a/src/Shared/AzureManaged/DurableTaskSchedulerOptions.cs
+++ b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs
@@ -4,13 +4,15 @@
using System.Globalization;
using Azure.Core;
using Azure.Identity;
+using Grpc.Core;
+using Grpc.Net.Client;
namespace Microsoft.DurableTask;
///
/// Options for configuring the Durable Task Scheduler.
///
-public class DurableTaskSchedulerOptions
+public class DurableTaskSchedulerWorkerOptions
{
///
/// Gets or sets the endpoint address of the Durable Task Scheduler resource.
@@ -53,7 +55,7 @@ public class DurableTaskSchedulerOptions
///
/// The connection string to parse.
/// A new instance of .
- public static DurableTaskSchedulerOptions FromConnectionString(string connectionString)
+ public static DurableTaskSchedulerWorkerOptions FromConnectionString(string connectionString)
{
return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
}
@@ -63,7 +65,7 @@ public static DurableTaskSchedulerOptions FromConnectionString(string connection
///
/// The connection string to parse.
/// A new instance of .
- public static DurableTaskSchedulerOptions FromConnectionString(
+ internal static DurableTaskSchedulerWorkerOptions FromConnectionString(
DurableTaskSchedulerConnectionString connectionString) => new()
{
EndpointAddress = connectionString.Endpoint,
diff --git a/src/Worker/AzureManaged/Worker.AzureManaged.csproj b/src/Worker/AzureManaged/Worker.AzureManaged.csproj
index e8647fa6..4985279f 100644
--- a/src/Worker/AzureManaged/Worker.AzureManaged.csproj
+++ b/src/Worker/AzureManaged/Worker.AzureManaged.csproj
@@ -8,7 +8,17 @@
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Client/AzureManaged.Tests/Client.AzureManaged.Tests.csproj b/test/Client/AzureManaged.Tests/Client.AzureManaged.Tests.csproj
index e19f0de9..309b5a3f 100644
--- a/test/Client/AzureManaged.Tests/Client.AzureManaged.Tests.csproj
+++ b/test/Client/AzureManaged.Tests/Client.AzureManaged.Tests.csproj
@@ -5,12 +5,16 @@
-
+
+
+
+
+
+
-
diff --git a/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs b/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs
index 5a0099dd..e05e352e 100644
--- a/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs
+++ b/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs
@@ -78,7 +78,7 @@ public void UseDurableTaskScheduler_WithNullParameters_ShouldThrowArgumentNullEx
var provider = services.BuildServiceProvider();
var ex = Assert.Throws(() =>
{
- var options = provider.GetRequiredService>().Value;
+ var options = provider.GetRequiredService>().Value;
});
Assert.Contains(endpoint == null ? "EndpointAddress" : "TaskHubName", ex.Message);
}
@@ -145,7 +145,7 @@ public void UseDurableTaskScheduler_WithNamedOptions_ShouldConfigureCorrectly()
// Assert
var provider = services.BuildServiceProvider();
- var optionsMonitor = provider.GetService>();
+ var optionsMonitor = provider.GetService>();
optionsMonitor.Should().NotBeNull();
var options = optionsMonitor!.Get("CustomName");
options.Should().NotBeNull();
@@ -159,7 +159,7 @@ public void ConfigureGrpcChannel_ShouldConfigureClientOptions()
{
// Arrange
var services = new ServiceCollection();
- services.AddOptions()
+ services.AddOptions()
.Configure(options =>
{
options.EndpointAddress = $"https://{ValidEndpoint}";
@@ -168,7 +168,7 @@ public void ConfigureGrpcChannel_ShouldConfigureClientOptions()
});
var provider = services.BuildServiceProvider();
- var schedulerOptions = provider.GetRequiredService>();
+ var schedulerOptions = provider.GetRequiredService>();
var configureGrpcChannel = new DurableTaskSchedulerClientExtensions.ConfigureGrpcChannel(schedulerOptions);
// Act
diff --git a/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientOptionsTests.cs b/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientOptionsTests.cs
new file mode 100644
index 00000000..7bc1577c
--- /dev/null
+++ b/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientOptionsTests.cs
@@ -0,0 +1,211 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Azure.Core;
+using Azure.Identity;
+using FluentAssertions;
+using Xunit;
+
+namespace Microsoft.DurableTask.Shared.AzureManaged.Tests;
+
+public class DurableTaskSchedulerClientOptionsTests
+{
+ private const string ValidEndpoint = "myaccount.westus3.durabletask.io";
+ private const string ValidTaskHub = "testhub";
+
+ [Fact]
+ public void FromConnectionString_WithDefaultAzure_ShouldCreateValidInstance()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
+
+ // Act
+ var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+
+ // Assert
+ options.EndpointAddress.Should().Be(ValidEndpoint);
+ options.TaskHubName.Should().Be(ValidTaskHub);
+ options.Credential.Should().BeOfType();
+ }
+
+ [Fact]
+ public void FromConnectionString_WithManagedIdentity_ShouldCreateValidInstance()
+ {
+ // Arrange
+ const string clientId = "00000000-0000-0000-0000-000000000000";
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=ManagedIdentity;ClientID={clientId};TaskHub={ValidTaskHub}";
+
+ // Act
+ var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+
+ // Assert
+ options.EndpointAddress.Should().Be(ValidEndpoint);
+ options.TaskHubName.Should().Be(ValidTaskHub);
+ options.Credential.Should().BeOfType();
+ }
+
+ [Fact]
+ public void FromConnectionString_WithWorkloadIdentity_ShouldCreateValidInstance()
+ {
+ // Arrange
+ const string clientId = "00000000-0000-0000-0000-000000000000";
+ const string tenantId = "11111111-1111-1111-1111-111111111111";
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=WorkloadIdentity;ClientID={clientId};TenantId={tenantId};TaskHub={ValidTaskHub}";
+
+ // Act
+ var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+
+ // Assert
+ options.EndpointAddress.Should().Be(ValidEndpoint);
+ options.TaskHubName.Should().Be(ValidTaskHub);
+ options.Credential.Should().BeOfType();
+ }
+
+ [Theory]
+ [InlineData("Environment")]
+ [InlineData("AzureCLI")]
+ [InlineData("AzurePowerShell")]
+ public void FromConnectionString_WithValidAuthTypes_ShouldCreateValidInstance(string authType)
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication={authType};TaskHub={ValidTaskHub}";
+
+ // Act
+ var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+
+ // Assert
+ options.EndpointAddress.Should().Be(ValidEndpoint);
+ options.TaskHubName.Should().Be(ValidTaskHub);
+ options.Credential.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void FromConnectionString_WithInvalidAuthType_ShouldThrowArgumentException()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=InvalidAuth;TaskHub={ValidTaskHub}";
+
+ // Act & Assert
+ var action = () => DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ action.Should().Throw()
+ .WithMessage("*contains an unsupported authentication type*");
+ }
+
+ [Fact]
+ public void FromConnectionString_WithMissingRequiredProperties_ShouldThrowArgumentNullException()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure"; // Missing TaskHub
+
+ // Act & Assert
+ var action = () => DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ action.Should().Throw();
+ }
+
+ [Fact]
+ public void FromConnectionString_WithNone_ShouldCreateInstanceWithNullCredential()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=None;TaskHub={ValidTaskHub}";
+
+ // Act
+ var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+
+ // Assert
+ options.EndpointAddress.Should().Be(ValidEndpoint);
+ options.TaskHubName.Should().Be(ValidTaskHub);
+ options.Credential.Should().BeNull();
+ }
+
+ [Fact]
+ public void DefaultProperties_ShouldHaveExpectedValues()
+ {
+ // Arrange & Act
+ var options = new DurableTaskSchedulerClientOptions();
+
+ // Assert
+ options.ResourceId.Should().Be("https://durabletask.io");
+ options.AllowInsecureCredentials.Should().BeFalse();
+ }
+
+ [Fact]
+ public void CreateChannel_WithHttpsEndpoint_ShouldCreateSecureChannel()
+ {
+ // Arrange
+ var options = new DurableTaskSchedulerClientOptions
+ {
+ EndpointAddress = $"https://{ValidEndpoint}",
+ TaskHubName = ValidTaskHub,
+ Credential = new DefaultAzureCredential()
+ };
+
+ // Act
+ var channel = options.CreateChannel();
+
+ // Assert
+ channel.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void CreateChannel_WithHttpEndpoint_ShouldCreateInsecureChannel()
+ {
+ // Arrange
+ var options = new DurableTaskSchedulerClientOptions
+ {
+ EndpointAddress = $"http://{ValidEndpoint}",
+ TaskHubName = ValidTaskHub,
+ AllowInsecureCredentials = true
+ };
+
+ // Act
+ var channel = options.CreateChannel();
+
+ // Assert
+ channel.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void FromConnectionString_WithInvalidEndpoint_ShouldThrowArgumentException()
+ {
+ // Arrange
+ var connectionString = "Endpoint=not a valid endpoint;Authentication=DefaultAzure;TaskHub=testhub;";
+
+ // Act & Assert
+ var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ var action = () => options.CreateChannel();
+ action.Should().Throw()
+ .WithMessage("Invalid URI: The hostname could not be parsed.");
+ }
+
+ [Fact]
+ public void FromConnectionString_WithoutProtocol_ShouldPreserveEndpoint()
+ {
+ // Arrange
+ string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
+
+ // Act
+ var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+
+ // Assert
+ options.EndpointAddress.Should().Be(ValidEndpoint);
+ }
+
+ [Fact]
+ public void CreateChannel_ShouldAddHttpsPrefix()
+ {
+ // Arrange
+ var options = new DurableTaskSchedulerClientOptions
+ {
+ EndpointAddress = ValidEndpoint,
+ TaskHubName = ValidTaskHub,
+ Credential = new DefaultAzureCredential()
+ };
+
+ // Act
+ var channel = options.CreateChannel();
+
+ // Assert
+ channel.Should().NotBeNull();
+ // Note: We can't directly test the endpoint in the channel as it's not exposed
+ }
+}
diff --git a/test/Shared/AzureManaged.Tests/AccessTokenCacheTests.cs b/test/Shared/AzureManaged.Tests/AccessTokenCacheTests.cs
index 525342b6..1be95032 100644
--- a/test/Shared/AzureManaged.Tests/AccessTokenCacheTests.cs
+++ b/test/Shared/AzureManaged.Tests/AccessTokenCacheTests.cs
@@ -3,8 +3,11 @@
using System.Threading.Tasks;
using Azure.Core;
using FluentAssertions;
+using Microsoft.DurableTask;
using Moq;
using Xunit;
+using System.Reflection;
+using DotNext;
namespace Microsoft.DurableTask.Shared.AzureManaged.Tests;
@@ -90,7 +93,7 @@ public async Task Constructor_WithNullCredential_ShouldThrowNullReferenceExcepti
// Act & Assert
// TODO: The constructor should validate its parameters and throw ArgumentNullException,
// but currently it allows null parameters and throws NullReferenceException when used.
- var action = () => cache.GetTokenAsync(cancellationToken);
+ Func action = () => cache.GetTokenAsync(cancellationToken);
await action.Should().ThrowAsync();
}
diff --git a/test/Shared/AzureManaged.Tests/Shared.AzureManaged.Tests.csproj b/test/Shared/AzureManaged.Tests/Shared.AzureManaged.Tests.csproj
index 8174e5c5..35864cba 100644
--- a/test/Shared/AzureManaged.Tests/Shared.AzureManaged.Tests.csproj
+++ b/test/Shared/AzureManaged.Tests/Shared.AzureManaged.Tests.csproj
@@ -4,15 +4,17 @@
net6.0
+
+
+
+
+
+
-
-
-
-
diff --git a/test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerExtensionsTests.cs b/test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerExtensionsTests.cs
index 2dd901b9..1ce3d803 100644
--- a/test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerExtensionsTests.cs
+++ b/test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerExtensionsTests.cs
@@ -78,7 +78,7 @@ public void UseDurableTaskScheduler_WithNullParameters_ShouldThrowArgumentNullEx
var provider = services.BuildServiceProvider();
var ex = Assert.Throws(() =>
{
- var options = provider.GetRequiredService>().Value;
+ var options = provider.GetRequiredService>().Value;
});
Assert.Contains(endpoint == null ? "EndpointAddress" : "TaskHubName", ex.Message);
}
@@ -145,7 +145,7 @@ public void UseDurableTaskScheduler_WithNamedOptions_ShouldConfigureCorrectly()
// Assert
var provider = services.BuildServiceProvider();
- var optionsMonitor = provider.GetService>();
+ var optionsMonitor = provider.GetService>();
optionsMonitor.Should().NotBeNull();
var options = optionsMonitor!.Get("CustomName");
options.Should().NotBeNull();
@@ -159,7 +159,7 @@ public void ConfigureGrpcChannel_ShouldConfigureWorkerOptions()
{
// Arrange
var services = new ServiceCollection();
- services.AddOptions()
+ services.AddOptions()
.Configure(options =>
{
options.EndpointAddress = $"https://{ValidEndpoint}";
@@ -168,7 +168,7 @@ public void ConfigureGrpcChannel_ShouldConfigureWorkerOptions()
});
var provider = services.BuildServiceProvider();
- var schedulerOptions = provider.GetRequiredService>();
+ var schedulerOptions = provider.GetRequiredService>();
var configureGrpcChannel = new DurableTaskSchedulerWorkerExtensions.ConfigureGrpcChannel(schedulerOptions);
// Act
diff --git a/test/Shared/AzureManaged.Tests/DurableTaskSchedulerOptionsTests.cs b/test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerOptionsTests.cs
similarity index 84%
rename from test/Shared/AzureManaged.Tests/DurableTaskSchedulerOptionsTests.cs
rename to test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerOptionsTests.cs
index 4c266cc1..1858fa3b 100644
--- a/test/Shared/AzureManaged.Tests/DurableTaskSchedulerOptionsTests.cs
+++ b/test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerOptionsTests.cs
@@ -8,7 +8,7 @@
namespace Microsoft.DurableTask.Shared.AzureManaged.Tests;
-public class DurableTaskSchedulerOptionsTests
+public class DurableTaskSchedulerWorkerOptionsTests
{
private const string ValidEndpoint = "myaccount.westus3.durabletask.io";
private const string ValidTaskHub = "testhub";
@@ -20,7 +20,7 @@ public void FromConnectionString_WithDefaultAzure_ShouldCreateValidInstance()
string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
// Act
- var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var options = DurableTaskSchedulerWorkerOptions.FromConnectionString(connectionString);
// Assert
options.EndpointAddress.Should().Be(ValidEndpoint);
@@ -36,7 +36,7 @@ public void FromConnectionString_WithManagedIdentity_ShouldCreateValidInstance()
string connectionString = $"Endpoint={ValidEndpoint};Authentication=ManagedIdentity;ClientID={clientId};TaskHub={ValidTaskHub}";
// Act
- var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var options = DurableTaskSchedulerWorkerOptions.FromConnectionString(connectionString);
// Assert
options.EndpointAddress.Should().Be(ValidEndpoint);
@@ -53,7 +53,7 @@ public void FromConnectionString_WithWorkloadIdentity_ShouldCreateValidInstance(
string connectionString = $"Endpoint={ValidEndpoint};Authentication=WorkloadIdentity;ClientID={clientId};TenantId={tenantId};TaskHub={ValidTaskHub}";
// Act
- var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var options = DurableTaskSchedulerWorkerOptions.FromConnectionString(connectionString);
// Assert
options.EndpointAddress.Should().Be(ValidEndpoint);
@@ -71,7 +71,7 @@ public void FromConnectionString_WithValidAuthTypes_ShouldCreateValidInstance(st
string connectionString = $"Endpoint={ValidEndpoint};Authentication={authType};TaskHub={ValidTaskHub}";
// Act
- var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var options = DurableTaskSchedulerWorkerOptions.FromConnectionString(connectionString);
// Assert
options.EndpointAddress.Should().Be(ValidEndpoint);
@@ -86,7 +86,7 @@ public void FromConnectionString_WithInvalidAuthType_ShouldThrowArgumentExceptio
string connectionString = $"Endpoint={ValidEndpoint};Authentication=InvalidAuth;TaskHub={ValidTaskHub}";
// Act & Assert
- var action = () => DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var action = () => DurableTaskSchedulerWorkerOptions.FromConnectionString(connectionString);
action.Should().Throw()
.WithMessage("*contains an unsupported authentication type*");
}
@@ -98,7 +98,7 @@ public void FromConnectionString_WithMissingRequiredProperties_ShouldThrowArgume
string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure"; // Missing TaskHub
// Act & Assert
- var action = () => DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var action = () => DurableTaskSchedulerWorkerOptions.FromConnectionString(connectionString);
action.Should().Throw();
}
@@ -109,7 +109,7 @@ public void FromConnectionString_WithNone_ShouldCreateInstanceWithNullCredential
string connectionString = $"Endpoint={ValidEndpoint};Authentication=None;TaskHub={ValidTaskHub}";
// Act
- var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var options = DurableTaskSchedulerWorkerOptions.FromConnectionString(connectionString);
// Assert
options.EndpointAddress.Should().Be(ValidEndpoint);
@@ -121,7 +121,7 @@ public void FromConnectionString_WithNone_ShouldCreateInstanceWithNullCredential
public void DefaultProperties_ShouldHaveExpectedValues()
{
// Arrange & Act
- var options = new DurableTaskSchedulerOptions();
+ var options = new DurableTaskSchedulerWorkerOptions();
// Assert
options.ResourceId.Should().Be("https://durabletask.io");
@@ -135,7 +135,7 @@ public void DefaultProperties_ShouldHaveExpectedValues()
public void CreateChannel_WithHttpsEndpoint_ShouldCreateSecureChannel()
{
// Arrange
- var options = new DurableTaskSchedulerOptions
+ var options = new DurableTaskSchedulerWorkerOptions
{
EndpointAddress = $"https://{ValidEndpoint}",
TaskHubName = ValidTaskHub,
@@ -153,7 +153,7 @@ public void CreateChannel_WithHttpsEndpoint_ShouldCreateSecureChannel()
public void CreateChannel_WithHttpEndpoint_ShouldCreateInsecureChannel()
{
// Arrange
- var options = new DurableTaskSchedulerOptions
+ var options = new DurableTaskSchedulerWorkerOptions
{
EndpointAddress = $"http://{ValidEndpoint}",
TaskHubName = ValidTaskHub,
@@ -174,7 +174,7 @@ public void FromConnectionString_WithInvalidEndpoint_ShouldThrowArgumentExceptio
var connectionString = "Endpoint=not a valid endpoint;Authentication=DefaultAzure;TaskHub=testhub;";
// Act & Assert
- var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var options = DurableTaskSchedulerWorkerOptions.FromConnectionString(connectionString);
var action = () => options.CreateChannel();
action.Should().Throw()
.WithMessage("Invalid URI: The hostname could not be parsed.");
@@ -187,7 +187,7 @@ public void FromConnectionString_WithoutProtocol_ShouldPreserveEndpoint()
string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
// Act
- var options = DurableTaskSchedulerOptions.FromConnectionString(connectionString);
+ var options = DurableTaskSchedulerWorkerOptions.FromConnectionString(connectionString);
// Assert
options.EndpointAddress.Should().Be(ValidEndpoint);
@@ -197,7 +197,7 @@ public void FromConnectionString_WithoutProtocol_ShouldPreserveEndpoint()
public void CreateChannel_ShouldAddHttpsPrefix()
{
// Arrange
- var options = new DurableTaskSchedulerOptions
+ var options = new DurableTaskSchedulerWorkerOptions
{
EndpointAddress = ValidEndpoint,
TaskHubName = ValidTaskHub,
diff --git a/test/Worker/AzureManaged.Tests/Worker.AzureManaged.Tests.csproj b/test/Worker/AzureManaged.Tests/Worker.AzureManaged.Tests.csproj
index 6e43cac1..6fb22508 100644
--- a/test/Worker/AzureManaged.Tests/Worker.AzureManaged.Tests.csproj
+++ b/test/Worker/AzureManaged.Tests/Worker.AzureManaged.Tests.csproj
@@ -10,8 +10,10 @@
-
+
+
+
From c697fbc59aab3e3bf841c8546358f3bd11a1ebcc Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Mon, 13 Jan 2025 20:33:51 -0800
Subject: [PATCH 50/58] refactor
---
.../DurableTaskSchedulerClientExtensions.cs | 53 +++++++++++--------
.../DurableTaskSchedulerWorkerExtensions.cs | 53 +++++++++++--------
.../DurableTaskSchedulerWorkerOptions.cs | 4 +-
...rableTaskSchedulerClientExtensionsTests.cs | 2 +-
4 files changed, 67 insertions(+), 45 deletions(-)
diff --git a/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
index a5432bf8..ecfb25ae 100644
--- a/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
+++ b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
@@ -29,20 +29,12 @@ public static void UseDurableTaskScheduler(
TokenCredential credential,
Action? configure = null)
{
- builder.Services.AddOptions(builder.Name)
- .Configure(options =>
- {
- options.EndpointAddress = endpointAddress;
- options.TaskHubName = taskHubName;
- options.Credential = credential;
- })
- .Configure(configure ?? (_ => { }))
- .ValidateDataAnnotations()
- .ValidateOnStart();
-
- builder.Services.TryAddEnumerable(
- ServiceDescriptor.Singleton, ConfigureGrpcChannel>());
- builder.UseGrpc(_ => { });
+ ConfigureSchedulerOptions(builder, options =>
+ {
+ options.EndpointAddress = endpointAddress;
+ options.TaskHubName = taskHubName;
+ options.Credential = credential;
+ }, configure);
}
///
@@ -57,15 +49,34 @@ public static void UseDurableTaskScheduler(
Action? configure = null)
{
var connectionOptions = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ ConfigureSchedulerOptions(builder, options =>
+ {
+ options.EndpointAddress = connectionOptions.EndpointAddress;
+ options.TaskHubName = connectionOptions.TaskHubName;
+ options.Credential = connectionOptions.Credential;
+ }, configure);
+ }
+ ///
+ /// Configures Durable Task client to use the Azure Durable Task Scheduler service using configuration options.
+ ///
+ /// The Durable Task client builder to configure.
+ /// Callback to configure DurableTaskSchedulerClientOptions.
+ public static void UseDurableTaskScheduler(
+ this IDurableTaskClientBuilder builder,
+ Action? configure = null)
+ {
+ ConfigureSchedulerOptions(builder, _ => { }, configure);
+ }
+
+ private static void ConfigureSchedulerOptions(
+ IDurableTaskClientBuilder builder,
+ Action initialConfig,
+ Action? additionalConfig)
+ {
builder.Services.AddOptions(builder.Name)
- .Configure(options =>
- {
- options.EndpointAddress = connectionOptions.EndpointAddress;
- options.TaskHubName = connectionOptions.TaskHubName;
- options.Credential = connectionOptions.Credential;
- })
- .Configure(configure ?? (_ => { }))
+ .Configure(initialConfig)
+ .Configure(additionalConfig ?? (_ => { }))
.ValidateDataAnnotations()
.ValidateOnStart();
diff --git a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
index 300e1492..0b7b3cc0 100644
--- a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
+++ b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
@@ -30,20 +30,12 @@ public static void UseDurableTaskScheduler(
TokenCredential credential,
Action? configure = null)
{
- builder.Services.AddOptions(builder.Name)
- .Configure(options =>
- {
- options.EndpointAddress = endpointAddress;
- options.TaskHubName = taskHubName;
- options.Credential = credential;
- })
- .Configure(configure ?? (_ => { }))
- .ValidateDataAnnotations()
- .ValidateOnStart();
-
- builder.Services.TryAddEnumerable(
- ServiceDescriptor.Singleton, ConfigureGrpcChannel>());
- builder.UseGrpc(_ => { });
+ ConfigureSchedulerOptions(builder, options =>
+ {
+ options.EndpointAddress = endpointAddress;
+ options.TaskHubName = taskHubName;
+ options.Credential = credential;
+ }, configure);
}
///
@@ -58,15 +50,34 @@ public static void UseDurableTaskScheduler(
Action? configure = null)
{
var connectionOptions = DurableTaskSchedulerWorkerOptions.FromConnectionString(connectionString);
+ ConfigureSchedulerOptions(builder, options =>
+ {
+ options.EndpointAddress = connectionOptions.EndpointAddress;
+ options.TaskHubName = connectionOptions.TaskHubName;
+ options.Credential = connectionOptions.Credential;
+ }, configure);
+ }
+ ///
+ /// Configures Durable Task worker to use the Azure Durable Task Scheduler service using configuration options.
+ ///
+ /// The Durable Task worker builder to configure.
+ /// Callback to configure DurableTaskSchedulerWorkerOptions.
+ public static void UseDurableTaskScheduler(
+ this IDurableTaskWorkerBuilder builder,
+ Action? configure = null)
+ {
+ ConfigureSchedulerOptions(builder, _ => { }, configure);
+ }
+
+ private static void ConfigureSchedulerOptions(
+ IDurableTaskWorkerBuilder builder,
+ Action initialConfig,
+ Action? additionalConfig)
+ {
builder.Services.AddOptions(builder.Name)
- .Configure(options =>
- {
- options.EndpointAddress = connectionOptions.EndpointAddress;
- options.TaskHubName = connectionOptions.TaskHubName;
- options.Credential = connectionOptions.Credential;
- })
- .Configure(configure ?? (_ => { }))
+ .Configure(initialConfig)
+ .Configure(additionalConfig ?? (_ => { }))
.ValidateDataAnnotations()
.ValidateOnStart();
diff --git a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs
index a7f79510..df704207 100644
--- a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs
+++ b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs
@@ -79,8 +79,8 @@ internal static DurableTaskSchedulerWorkerOptions FromConnectionString(
/// A configured instance that can be used to make gRPC calls.
public GrpcChannel CreateChannel()
{
- Check.NotNullOrEmpty(this.EndpointAddress, nameof(this.EndpointAddress));
- Check.NotNullOrEmpty(this.TaskHubName, nameof(this.TaskHubName));
+ Verify.NotNull(this.EndpointAddress, nameof(this.EndpointAddress));
+ Verify.NotNull(this.TaskHubName, nameof(this.TaskHubName));
string taskHubName = this.TaskHubName;
string endpoint = !this.EndpointAddress.Contains("://")
? $"https://{this.EndpointAddress}"
diff --git a/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs b/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs
index e05e352e..f9131c34 100644
--- a/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs
+++ b/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.ComponentModel.DataAnnotations;
using Azure.Core;
using Azure.Identity;
using FluentAssertions;
@@ -10,7 +11,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
-using System.ComponentModel.DataAnnotations;
using Xunit;
namespace Microsoft.DurableTask.Client.AzureManaged.Tests;
From 55eea5494dd2dbfd2e36cfbcde7fcdb5dc049d48 Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Mon, 13 Jan 2025 20:59:35 -0800
Subject: [PATCH 51/58] fix
---
.../AzureManaged/DurableTaskSchedulerClientOptions.cs | 8 ++++----
.../AzureManaged/DurableTaskSchedulerWorkerExtensions.cs | 4 ++--
.../AzureManaged/DurableTaskSchedulerWorkerOptions.cs | 8 ++++----
3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs b/src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs
index 30ab86a3..12b08497 100644
--- a/src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs
+++ b/src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs
@@ -51,20 +51,20 @@ public class DurableTaskSchedulerClientOptions
public bool AllowInsecureCredentials { get; set; }
///
- /// Creates a new instance of from a connection string.
+ /// Creates a new instance of from a connection string.
///
/// The connection string to parse.
- /// A new instance of .
+ /// A new instance of .
public static DurableTaskSchedulerClientOptions FromConnectionString(string connectionString)
{
return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
}
///
- /// Creates a new instance of from a parsed connection string.
+ /// Creates a new instance of from a parsed connection string.
///
/// The connection string to parse.
- /// A new instance of .
+ /// A new instance of .
internal static DurableTaskSchedulerClientOptions FromConnectionString(
DurableTaskSchedulerConnectionString connectionString) => new()
{
diff --git a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
index 0b7b3cc0..64219a8e 100644
--- a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
+++ b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
@@ -22,7 +22,7 @@ public static class DurableTaskSchedulerWorkerExtensions
/// The endpoint address of the Durable Task Scheduler resource. Expected to be in the format "https://{scheduler-name}.{region}.durabletask.io".
/// The name of the task hub resource associated with the Durable Task Scheduler resource.
/// The credential used to authenticate with the Durable Task Scheduler task hub resource.
- /// Optional callback to dynamically configure DurableTaskSchedulerOptions.
+ /// Optional callback to dynamically configure DurableTaskSchedulerWorkerOptions.
public static void UseDurableTaskScheduler(
this IDurableTaskWorkerBuilder builder,
string endpointAddress,
@@ -43,7 +43,7 @@ public static void UseDurableTaskScheduler(
///
/// The Durable Task worker builder to configure.
/// The connection string used to connect to the Durable Task Scheduler service.
- /// Optional callback to dynamically configure DurableTaskSchedulerOptions.
+ /// Optional callback to dynamically configure DurableTaskSchedulerWorkerOptions.
public static void UseDurableTaskScheduler(
this IDurableTaskWorkerBuilder builder,
string connectionString,
diff --git a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs
index df704207..b5df6fa6 100644
--- a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs
+++ b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs
@@ -51,20 +51,20 @@ public class DurableTaskSchedulerWorkerOptions
public bool AllowInsecureCredentials { get; set; }
///
- /// Creates a new instance of from a connection string.
+ /// Creates a new instance of from a connection string.
///
/// The connection string to parse.
- /// A new instance of .
+ /// A new instance of .
public static DurableTaskSchedulerWorkerOptions FromConnectionString(string connectionString)
{
return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
}
///
- /// Creates a new instance of from a parsed connection string.
+ /// Creates a new instance of from a parsed connection string.
///
/// The connection string to parse.
- /// A new instance of .
+ /// A new instance of .
internal static DurableTaskSchedulerWorkerOptions FromConnectionString(
DurableTaskSchedulerConnectionString connectionString) => new()
{
From 1ede0d4352014d76207cef961e8fdf8a4cfc60fe Mon Sep 17 00:00:00 2001
From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com>
Date: Tue, 14 Jan 2025 23:51:11 -0800
Subject: [PATCH 52/58] fb
---
.editorconfig | 8 +-
.../AzureManaged/Client.AzureManaged.csproj | 2 +-
.../DurableTaskSchedulerClientExtensions.cs | 36 ++++---
.../DurableTaskSchedulerClientOptions.cs | 38 +++----
.../DurableTaskSchedulerWorkerExtensions.cs | 37 ++++---
.../DurableTaskSchedulerWorkerOptions.cs | 31 +++---
.../AzureManaged/Worker.AzureManaged.csproj | 2 +-
.../Client.AzureManaged.Tests.csproj | 3 +-
...rableTaskSchedulerClientExtensionsTests.cs | 101 +++++++-----------
.../DurableTaskSchedulerClientOptionsTests.cs | 41 ++++---
.../AccessTokenCacheTests.cs | 81 +++++++-------
...rableTaskSchedulerConnectionStringTests.cs | 39 ++++---
.../Shared.AzureManaged.Tests.csproj | 2 +-
...rableTaskSchedulerWorkerExtensionsTests.cs | 101 +++++++-----------
.../DurableTaskSchedulerWorkerOptionsTests.cs | 41 ++++---
.../Worker.AzureManaged.Tests.csproj | 2 +-
16 files changed, 254 insertions(+), 311 deletions(-)
diff --git a/.editorconfig b/.editorconfig
index 7c1faebc..23881078 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,4 +1,4 @@
-# Remove the line below if you want to inherit .editorconfig settings from higher directories
+'# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
# XML project files
@@ -232,3 +232,9 @@ dotnet_diagnostic.SA1400.severity = none # Access modifier must be declared
dotnet_diagnostic.SA1402.severity = none # File may only contain a single type
dotnet_diagnostic.SA1633.severity = warning # File must have header -- TODO: replace with IDE0073 eventually
dotnet_diagnostic.SA1649.severity = none # File name must match type name
+
+# Enforce explicit types instead of `var`
+dotnet_style_prefer_var_for_built_in_types = false:error
+dotnet_style_prefer_var_for_simple_types = false:error
+dotnet_style_prefer_var_when_type_is_apparent = false:error
+dotnet_style_prefer_var_when_type_is_not_apparent = false:error
diff --git a/src/Client/AzureManaged/Client.AzureManaged.csproj b/src/Client/AzureManaged/Client.AzureManaged.csproj
index 29f0dfd8..0b80078b 100644
--- a/src/Client/AzureManaged/Client.AzureManaged.csproj
+++ b/src/Client/AzureManaged/Client.AzureManaged.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net6.0
Azure Managed extensions for the Durable Task Framework client.
true
diff --git a/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
index ecfb25ae..290b09a8 100644
--- a/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
+++ b/src/Client/AzureManaged/DurableTaskSchedulerClientExtensions.cs
@@ -29,12 +29,15 @@ public static void UseDurableTaskScheduler(
TokenCredential credential,
Action? configure = null)
{
- ConfigureSchedulerOptions(builder, options =>
- {
- options.EndpointAddress = endpointAddress;
- options.TaskHubName = taskHubName;
- options.Credential = credential;
- }, configure);
+ ConfigureSchedulerOptions(
+ builder,
+ options =>
+ {
+ options.EndpointAddress = endpointAddress;
+ options.TaskHubName = taskHubName;
+ options.Credential = credential;
+ },
+ configure);
}
///
@@ -49,12 +52,15 @@ public static void UseDurableTaskScheduler(
Action? configure = null)
{
var connectionOptions = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
- ConfigureSchedulerOptions(builder, options =>
- {
- options.EndpointAddress = connectionOptions.EndpointAddress;
- options.TaskHubName = connectionOptions.TaskHubName;
- options.Credential = connectionOptions.Credential;
- }, configure);
+ ConfigureSchedulerOptions(
+ builder,
+ options =>
+ {
+ options.EndpointAddress = connectionOptions.EndpointAddress;
+ options.TaskHubName = connectionOptions.TaskHubName;
+ options.Credential = connectionOptions.Credential;
+ },
+ configure);
}
///
@@ -69,7 +75,7 @@ public static void UseDurableTaskScheduler(
ConfigureSchedulerOptions(builder, _ => { }, configure);
}
- private static void ConfigureSchedulerOptions(
+ static void ConfigureSchedulerOptions(
IDurableTaskClientBuilder builder,
Action initialConfig,
Action? additionalConfig)
@@ -86,11 +92,11 @@ private static void ConfigureSchedulerOptions(
}
///
- /// Internal configuration class that sets up gRPC channels for client options
+ /// Configuration class that sets up gRPC channels for client options
/// using the provided Durable Task Scheduler options.
///
/// Monitor for accessing the current scheduler options configuration.
- internal class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
+ class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
IConfigureNamedOptions
{
///
diff --git a/src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs b/src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs
index 12b08497..8278a2d8 100644
--- a/src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs
+++ b/src/Client/AzureManaged/DurableTaskSchedulerClientOptions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.ComponentModel.DataAnnotations;
-using System.Globalization;
using Azure.Core;
using Azure.Identity;
using Grpc.Core;
@@ -38,15 +37,9 @@ public class DurableTaskSchedulerClientOptions
///
public string ResourceId { get; set; } = "https://durabletask.io";
- ///
- /// Gets or sets the worker ID used to identify the worker instance.
- /// The default value is a string containing the machine name, process ID, and a unique identifier.
- ///
- public string WorkerId { get; set; } = $"{Environment.MachineName},{Environment.ProcessId},{Guid.NewGuid():N}";
-
///
/// Gets or sets a value indicating whether to allow insecure channel credentials.
- /// This should only be set to true in development/testing scenarios.
+ /// This should only be set to true in local development/testing scenarios.
///
public bool AllowInsecureCredentials { get; set; }
@@ -60,19 +53,6 @@ public static DurableTaskSchedulerClientOptions FromConnectionString(string conn
return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
}
- ///
- /// Creates a new instance of from a parsed connection string.
- ///
- /// The connection string to parse.
- /// A new instance of .
- internal static DurableTaskSchedulerClientOptions FromConnectionString(
- DurableTaskSchedulerConnectionString connectionString) => new()
- {
- EndpointAddress = connectionString.Endpoint,
- TaskHubName = connectionString.TaskHubName,
- Credential = GetCredentialFromConnectionString(connectionString),
- };
-
///
/// Creates a gRPC channel for communicating with the Durable Task Scheduler service.
///
@@ -96,7 +76,6 @@ this.Credential is not null
async (context, metadata) =>
{
metadata.Add("taskhub", taskHubName);
- metadata.Add("workerid", this.WorkerId);
if (cache == null)
{
return;
@@ -117,12 +96,25 @@ this.Credential is not null
});
}
+ ///
+ /// Creates a new instance of from a parsed connection string.
+ ///
+ /// The connection string to parse.
+ /// A new instance of .
+ internal static DurableTaskSchedulerClientOptions FromConnectionString(
+ DurableTaskSchedulerConnectionString connectionString) => new()
+ {
+ EndpointAddress = connectionString.Endpoint,
+ TaskHubName = connectionString.TaskHubName,
+ Credential = GetCredentialFromConnectionString(connectionString),
+ };
+
static TokenCredential? GetCredentialFromConnectionString(DurableTaskSchedulerConnectionString connectionString)
{
string authType = connectionString.Authentication;
// Parse the supported auth types, in a case-insensitive way and without spaces
- switch (authType.ToLower(CultureInfo.InvariantCulture).Replace(" ", string.Empty))
+ switch (authType.ToLowerInvariant())
{
case "defaultazure":
return new DefaultAzureCredential();
diff --git a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
index 64219a8e..71d7aa1e 100644
--- a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
+++ b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerExtensions.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT License.
using Azure.Core;
-using Microsoft.DurableTask.Worker;
using Microsoft.DurableTask.Worker.Grpc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -30,12 +29,15 @@ public static void UseDurableTaskScheduler(
TokenCredential credential,
Action? configure = null)
{
- ConfigureSchedulerOptions(builder, options =>
- {
- options.EndpointAddress = endpointAddress;
- options.TaskHubName = taskHubName;
- options.Credential = credential;
- }, configure);
+ ConfigureSchedulerOptions(
+ builder,
+ options =>
+ {
+ options.EndpointAddress = endpointAddress;
+ options.TaskHubName = taskHubName;
+ options.Credential = credential;
+ },
+ configure);
}
///
@@ -50,12 +52,15 @@ public static void UseDurableTaskScheduler(
Action? configure = null)
{
var connectionOptions = DurableTaskSchedulerWorkerOptions.FromConnectionString(connectionString);
- ConfigureSchedulerOptions(builder, options =>
- {
- options.EndpointAddress = connectionOptions.EndpointAddress;
- options.TaskHubName = connectionOptions.TaskHubName;
- options.Credential = connectionOptions.Credential;
- }, configure);
+ ConfigureSchedulerOptions(
+ builder,
+ options =>
+ {
+ options.EndpointAddress = connectionOptions.EndpointAddress;
+ options.TaskHubName = connectionOptions.TaskHubName;
+ options.Credential = connectionOptions.Credential;
+ },
+ configure);
}
///
@@ -70,7 +75,7 @@ public static void UseDurableTaskScheduler(
ConfigureSchedulerOptions(builder, _ => { }, configure);
}
- private static void ConfigureSchedulerOptions(
+ static void ConfigureSchedulerOptions(
IDurableTaskWorkerBuilder builder,
Action initialConfig,
Action? additionalConfig)
@@ -87,11 +92,11 @@ private static void ConfigureSchedulerOptions(
}
///
- /// Internal configuration class that sets up gRPC channels for worker options
+ /// Configuration class that sets up gRPC channels for worker options
/// using the provided Durable Task Scheduler options.
///
/// Monitor for accessing the current scheduler options configuration.
- internal class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
+ class ConfigureGrpcChannel(IOptionsMonitor schedulerOptions) :
IConfigureNamedOptions
{
///
diff --git a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs
index b5df6fa6..4cc32735 100644
--- a/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs
+++ b/src/Worker/AzureManaged/DurableTaskSchedulerWorkerOptions.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.ComponentModel.DataAnnotations;
-using System.Globalization;
using Azure.Core;
using Azure.Identity;
using Grpc.Core;
@@ -46,7 +45,7 @@ public class DurableTaskSchedulerWorkerOptions
///
/// Gets or sets a value indicating whether to allow insecure channel credentials.
- /// This should only be set to true in development/testing scenarios.
+ /// This should only be set to true in local development/testing scenarios.
///
public bool AllowInsecureCredentials { get; set; }
@@ -60,19 +59,6 @@ public static DurableTaskSchedulerWorkerOptions FromConnectionString(string conn
return FromConnectionString(new DurableTaskSchedulerConnectionString(connectionString));
}
- ///
- /// Creates a new instance of from a parsed connection string.
- ///
- /// The connection string to parse.
- /// A new instance of .
- internal static DurableTaskSchedulerWorkerOptions FromConnectionString(
- DurableTaskSchedulerConnectionString connectionString) => new()
- {
- EndpointAddress = connectionString.Endpoint,
- TaskHubName = connectionString.TaskHubName,
- Credential = GetCredentialFromConnectionString(connectionString),
- };
-
///
/// Creates a gRPC channel for communicating with the Durable Task Scheduler service.
///
@@ -117,12 +103,25 @@ this.Credential is not null
});
}
+ ///
+ /// Creates a new instance of from a parsed connection string.
+ ///
+ /// The connection string to parse.
+ /// A new instance of .
+ internal static DurableTaskSchedulerWorkerOptions FromConnectionString(
+ DurableTaskSchedulerConnectionString connectionString) => new()
+ {
+ EndpointAddress = connectionString.Endpoint,
+ TaskHubName = connectionString.TaskHubName,
+ Credential = GetCredentialFromConnectionString(connectionString),
+ };
+
static TokenCredential? GetCredentialFromConnectionString(DurableTaskSchedulerConnectionString connectionString)
{
string authType = connectionString.Authentication;
// Parse the supported auth types, in a case-insensitive way and without spaces
- switch (authType.ToLower(CultureInfo.InvariantCulture).Replace(" ", string.Empty))
+ switch (authType.ToLowerInvariant())
{
case "defaultazure":
return new DefaultAzureCredential();
diff --git a/src/Worker/AzureManaged/Worker.AzureManaged.csproj b/src/Worker/AzureManaged/Worker.AzureManaged.csproj
index 4985279f..74df2840 100644
--- a/src/Worker/AzureManaged/Worker.AzureManaged.csproj
+++ b/src/Worker/AzureManaged/Worker.AzureManaged.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net6.0
Azure Managed extensions for the Durable Task Framework worker.
true
diff --git a/test/Client/AzureManaged.Tests/Client.AzureManaged.Tests.csproj b/test/Client/AzureManaged.Tests/Client.AzureManaged.Tests.csproj
index 309b5a3f..d7f4726c 100644
--- a/test/Client/AzureManaged.Tests/Client.AzureManaged.Tests.csproj
+++ b/test/Client/AzureManaged.Tests/Client.AzureManaged.Tests.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net6.0
@@ -10,7 +10,6 @@
-
diff --git a/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs b/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs
index f9131c34..76eb4ab2 100644
--- a/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs
+++ b/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientExtensionsTests.cs
@@ -1,12 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System.ComponentModel.DataAnnotations;
using Azure.Core;
using Azure.Identity;
using FluentAssertions;
-using Grpc.Net.Client;
-using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.Grpc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@@ -17,24 +14,24 @@ namespace Microsoft.DurableTask.Client.AzureManaged.Tests;
public class DurableTaskSchedulerClientExtensionsTests
{
- private const string ValidEndpoint = "myaccount.westus3.durabletask.io";
- private const string ValidTaskHub = "testhub";
+ const string ValidEndpoint = "myaccount.westus3.durabletask.io";
+ const string ValidTaskHub = "testhub";
[Fact]
public void UseDurableTaskScheduler_WithEndpointAndCredential_ShouldConfigureCorrectly()
{
// Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
+ ServiceCollection services = new ServiceCollection();
+ Mock mockBuilder = new Mock();
mockBuilder.Setup(b => b.Services).Returns(services);
- var credential = new DefaultAzureCredential();
+ DefaultAzureCredential credential = new DefaultAzureCredential();
// Act
mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential);
// Assert
- var provider = services.BuildServiceProvider();
- var options = provider.GetService>();
+ ServiceProvider provider = services.BuildServiceProvider();
+ IOptions? options = provider.GetService>();
options.Should().NotBeNull();
}
@@ -42,8 +39,8 @@ public void UseDurableTaskScheduler_WithEndpointAndCredential_ShouldConfigureCor
public void UseDurableTaskScheduler_WithConnectionString_ShouldConfigureCorrectly()
{
// Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
+ ServiceCollection services = new ServiceCollection();
+ Mock mockBuilder = new Mock();
mockBuilder.Setup(b => b.Services).Returns(services);
string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
@@ -51,8 +48,8 @@ public void UseDurableTaskScheduler_WithConnectionString_ShouldConfigureCorrectl
mockBuilder.Object.UseDurableTaskScheduler(connectionString);
// Assert
- var provider = services.BuildServiceProvider();
- var options = provider.GetService>();
+ ServiceProvider provider = services.BuildServiceProvider();
+ IOptions? options = provider.GetService>();
options.Should().NotBeNull();
}
@@ -62,23 +59,23 @@ public void UseDurableTaskScheduler_WithConnectionString_ShouldConfigureCorrectl
public void UseDurableTaskScheduler_WithNullParameters_ShouldThrowArgumentNullException(string endpoint, string taskHub)
{
// Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
+ ServiceCollection services = new ServiceCollection();
+ Mock mockBuilder = new Mock();
mockBuilder.Setup(b => b.Services).Returns(services);
- var credential = new DefaultAzureCredential();
+ DefaultAzureCredential credential = new DefaultAzureCredential();
// Act
- var action = () => mockBuilder.Object.UseDurableTaskScheduler(endpoint, taskHub, credential);
+ Action action = () => mockBuilder.Object.UseDurableTaskScheduler(endpoint, taskHub, credential);
// Assert
action.Should().NotThrow(); // The validation happens when building the service provider
-
+
if (endpoint == null || taskHub == null)
{
- var provider = services.BuildServiceProvider();
- var ex = Assert.Throws(() =>
+ ServiceProvider provider = services.BuildServiceProvider();
+ OptionsValidationException ex = Assert.Throws(() =>
{
- var options = provider.GetRequiredService>().Value;
+ DurableTaskSchedulerClientOptions options = provider.GetRequiredService>().Value;
});
Assert.Contains(endpoint == null ? "EndpointAddress" : "TaskHubName", ex.Message);
}
@@ -88,13 +85,13 @@ public void UseDurableTaskScheduler_WithNullParameters_ShouldThrowArgumentNullEx
public void UseDurableTaskScheduler_WithNullCredential_ShouldSucceed()
{
// Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
+ ServiceCollection services = new ServiceCollection();
+ Mock mockBuilder = new Mock();
mockBuilder.Setup(b => b.Services).Returns(services);
TokenCredential? credential = null;
// Act & Assert
- var action = () => mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential!);
+ Action action = () => mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential!);
action.Should().NotThrow();
}
@@ -102,13 +99,13 @@ public void UseDurableTaskScheduler_WithNullCredential_ShouldSucceed()
public void UseDurableTaskScheduler_WithInvalidConnectionString_ShouldThrowArgumentException()
{
// Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
+ ServiceCollection services = new ServiceCollection();
+ Mock mockBuilder = new Mock();
mockBuilder.Setup(b => b.Services).Returns(services);
- var connectionString = "This is not a valid=connection string format";
+ string connectionString = "This is not a valid=connection string format";
// Act
- var action = () => mockBuilder.Object.UseDurableTaskScheduler(connectionString);
+ Action action = () => mockBuilder.Object.UseDurableTaskScheduler(connectionString);
// Assert
action.Should().Throw()
@@ -121,12 +118,12 @@ public void UseDurableTaskScheduler_WithInvalidConnectionString_ShouldThrowArgum
public void UseDurableTaskScheduler_WithNullOrEmptyConnectionString_ShouldThrowArgumentException(string connectionString)
{
// Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
+ ServiceCollection services = new ServiceCollection();
+ Mock mockBuilder = new Mock();
mockBuilder.Setup(b => b.Services).Returns(services);
// Act & Assert
- var action = () => mockBuilder.Object.UseDurableTaskScheduler(connectionString);
+ Action action = () => mockBuilder.Object.UseDurableTaskScheduler(connectionString);
action.Should().Throw();
}
@@ -134,49 +131,23 @@ public void UseDurableTaskScheduler_WithNullOrEmptyConnectionString_ShouldThrowA
public void UseDurableTaskScheduler_WithNamedOptions_ShouldConfigureCorrectly()
{
// Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
+ ServiceCollection services = new ServiceCollection();
+ Mock mockBuilder = new Mock();
mockBuilder.Setup(b => b.Services).Returns(services);
mockBuilder.Setup(b => b.Name).Returns("CustomName");
- var credential = new DefaultAzureCredential();
+ DefaultAzureCredential credential = new DefaultAzureCredential();
// Act
mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential);
// Assert
- var provider = services.BuildServiceProvider();
- var optionsMonitor = provider.GetService>();
+ ServiceProvider provider = services.BuildServiceProvider();
+ IOptionsMonitor? optionsMonitor = provider.GetService>();
optionsMonitor.Should().NotBeNull();
- var options = optionsMonitor!.Get("CustomName");
+ DurableTaskSchedulerClientOptions options = optionsMonitor!.Get("CustomName");
options.Should().NotBeNull();
options.EndpointAddress.Should().Be(ValidEndpoint); // The https:// prefix is added by CreateChannel, not in the extension method
options.TaskHubName.Should().Be(ValidTaskHub);
options.Credential.Should().BeOfType();
}
-
- [Fact]
- public void ConfigureGrpcChannel_ShouldConfigureClientOptions()
- {
- // Arrange
- var services = new ServiceCollection();
- services.AddOptions()
- .Configure(options =>
- {
- options.EndpointAddress = $"https://{ValidEndpoint}";
- options.TaskHubName = ValidTaskHub;
- options.Credential = new DefaultAzureCredential();
- });
-
- var provider = services.BuildServiceProvider();
- var schedulerOptions = provider.GetRequiredService>();
- var configureGrpcChannel = new DurableTaskSchedulerClientExtensions.ConfigureGrpcChannel(schedulerOptions);
-
- // Act
- var clientOptions = new GrpcDurableTaskClientOptions();
- configureGrpcChannel.Configure(clientOptions);
-
- // Assert
- clientOptions.Channel.Should().NotBeNull();
- clientOptions.Channel.Should().BeOfType();
- }
-}
+}
diff --git a/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientOptionsTests.cs b/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientOptionsTests.cs
index 7bc1577c..ff0fc43e 100644
--- a/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientOptionsTests.cs
+++ b/test/Client/AzureManaged.Tests/DurableTaskSchedulerClientOptionsTests.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using Azure.Core;
using Azure.Identity;
using FluentAssertions;
using Xunit;
@@ -10,8 +9,8 @@ namespace Microsoft.DurableTask.Shared.AzureManaged.Tests;
public class DurableTaskSchedulerClientOptionsTests
{
- private const string ValidEndpoint = "myaccount.westus3.durabletask.io";
- private const string ValidTaskHub = "testhub";
+ const string ValidEndpoint = "myaccount.westus3.durabletask.io";
+ const string ValidTaskHub = "testhub";
[Fact]
public void FromConnectionString_WithDefaultAzure_ShouldCreateValidInstance()
@@ -20,7 +19,7 @@ public void FromConnectionString_WithDefaultAzure_ShouldCreateValidInstance()
string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
// Act
- var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ DurableTaskSchedulerClientOptions options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
// Assert
options.EndpointAddress.Should().Be(ValidEndpoint);
@@ -36,7 +35,7 @@ public void FromConnectionString_WithManagedIdentity_ShouldCreateValidInstance()
string connectionString = $"Endpoint={ValidEndpoint};Authentication=ManagedIdentity;ClientID={clientId};TaskHub={ValidTaskHub}";
// Act
- var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ DurableTaskSchedulerClientOptions options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
// Assert
options.EndpointAddress.Should().Be(ValidEndpoint);
@@ -53,7 +52,7 @@ public void FromConnectionString_WithWorkloadIdentity_ShouldCreateValidInstance(
string connectionString = $"Endpoint={ValidEndpoint};Authentication=WorkloadIdentity;ClientID={clientId};TenantId={tenantId};TaskHub={ValidTaskHub}";
// Act
- var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ DurableTaskSchedulerClientOptions options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
// Assert
options.EndpointAddress.Should().Be(ValidEndpoint);
@@ -71,7 +70,7 @@ public void FromConnectionString_WithValidAuthTypes_ShouldCreateValidInstance(st
string connectionString = $"Endpoint={ValidEndpoint};Authentication={authType};TaskHub={ValidTaskHub}";
// Act
- var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ DurableTaskSchedulerClientOptions options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
// Assert
options.EndpointAddress.Should().Be(ValidEndpoint);
@@ -86,7 +85,7 @@ public void FromConnectionString_WithInvalidAuthType_ShouldThrowArgumentExceptio
string connectionString = $"Endpoint={ValidEndpoint};Authentication=InvalidAuth;TaskHub={ValidTaskHub}";
// Act & Assert
- var action = () => DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ Action action = () => DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
action.Should().Throw()
.WithMessage("*contains an unsupported authentication type*");
}
@@ -98,7 +97,7 @@ public void FromConnectionString_WithMissingRequiredProperties_ShouldThrowArgume
string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure"; // Missing TaskHub
// Act & Assert
- var action = () => DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ Action action = () => DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
action.Should().Throw();
}
@@ -109,7 +108,7 @@ public void FromConnectionString_WithNone_ShouldCreateInstanceWithNullCredential
string connectionString = $"Endpoint={ValidEndpoint};Authentication=None;TaskHub={ValidTaskHub}";
// Act
- var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ DurableTaskSchedulerClientOptions options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
// Assert
options.EndpointAddress.Should().Be(ValidEndpoint);
@@ -121,7 +120,7 @@ public void FromConnectionString_WithNone_ShouldCreateInstanceWithNullCredential
public void DefaultProperties_ShouldHaveExpectedValues()
{
// Arrange & Act
- var options = new DurableTaskSchedulerClientOptions();
+ DurableTaskSchedulerClientOptions options = new DurableTaskSchedulerClientOptions();
// Assert
options.ResourceId.Should().Be("https://durabletask.io");
@@ -132,7 +131,7 @@ public void DefaultProperties_ShouldHaveExpectedValues()
public void CreateChannel_WithHttpsEndpoint_ShouldCreateSecureChannel()
{
// Arrange
- var options = new DurableTaskSchedulerClientOptions
+ DurableTaskSchedulerClientOptions options = new DurableTaskSchedulerClientOptions
{
EndpointAddress = $"https://{ValidEndpoint}",
TaskHubName = ValidTaskHub,
@@ -140,7 +139,7 @@ public void CreateChannel_WithHttpsEndpoint_ShouldCreateSecureChannel()
};
// Act
- var channel = options.CreateChannel();
+ Grpc.Core.ChannelBase channel = options.CreateChannel();
// Assert
channel.Should().NotBeNull();
@@ -150,7 +149,7 @@ public void CreateChannel_WithHttpsEndpoint_ShouldCreateSecureChannel()
public void CreateChannel_WithHttpEndpoint_ShouldCreateInsecureChannel()
{
// Arrange
- var options = new DurableTaskSchedulerClientOptions
+ DurableTaskSchedulerClientOptions options = new DurableTaskSchedulerClientOptions
{
EndpointAddress = $"http://{ValidEndpoint}",
TaskHubName = ValidTaskHub,
@@ -158,7 +157,7 @@ public void CreateChannel_WithHttpEndpoint_ShouldCreateInsecureChannel()
};
// Act
- var channel = options.CreateChannel();
+ Grpc.Core.ChannelBase channel = options.CreateChannel();
// Assert
channel.Should().NotBeNull();
@@ -168,11 +167,11 @@ public void CreateChannel_WithHttpEndpoint_ShouldCreateInsecureChannel()
public void FromConnectionString_WithInvalidEndpoint_ShouldThrowArgumentException()
{
// Arrange
- var connectionString = "Endpoint=not a valid endpoint;Authentication=DefaultAzure;TaskHub=testhub;";
+ string connectionString = "Endpoint=not a valid endpoint;Authentication=DefaultAzure;TaskHub=testhub;";
// Act & Assert
- var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
- var action = () => options.CreateChannel();
+ DurableTaskSchedulerClientOptions options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ Action action = () => options.CreateChannel();
action.Should().Throw()
.WithMessage("Invalid URI: The hostname could not be parsed.");
}
@@ -184,7 +183,7 @@ public void FromConnectionString_WithoutProtocol_ShouldPreserveEndpoint()
string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
// Act
- var options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
+ DurableTaskSchedulerClientOptions options = DurableTaskSchedulerClientOptions.FromConnectionString(connectionString);
// Assert
options.EndpointAddress.Should().Be(ValidEndpoint);
@@ -194,7 +193,7 @@ public void FromConnectionString_WithoutProtocol_ShouldPreserveEndpoint()
public void CreateChannel_ShouldAddHttpsPrefix()
{
// Arrange
- var options = new DurableTaskSchedulerClientOptions
+ DurableTaskSchedulerClientOptions options = new DurableTaskSchedulerClientOptions
{
EndpointAddress = ValidEndpoint,
TaskHubName = ValidTaskHub,
@@ -202,7 +201,7 @@ public void CreateChannel_ShouldAddHttpsPrefix()
};
// Act
- var channel = options.CreateChannel();
+ Grpc.Core.ChannelBase channel = options.CreateChannel();
// Assert
channel.Should().NotBeNull();
diff --git a/test/Shared/AzureManaged.Tests/AccessTokenCacheTests.cs b/test/Shared/AzureManaged.Tests/AccessTokenCacheTests.cs
index 1be95032..4ec0c825 100644
--- a/test/Shared/AzureManaged.Tests/AccessTokenCacheTests.cs
+++ b/test/Shared/AzureManaged.Tests/AccessTokenCacheTests.cs
@@ -1,99 +1,96 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
using Azure.Core;
using FluentAssertions;
-using Microsoft.DurableTask;
using Moq;
using Xunit;
-using System.Reflection;
-using DotNext;
namespace Microsoft.DurableTask.Shared.AzureManaged.Tests;
public class AccessTokenCacheTests
{
- private readonly Mock mockCredential;
- private readonly TokenRequestContext tokenRequestContext;
- private readonly TimeSpan margin;
- private readonly CancellationToken cancellationToken;
+ readonly Mock mockCredential;
+ readonly TokenRequestContext tokenRequestContext;
+ readonly TimeSpan margin;
+ readonly CancellationToken cancellationToken;
public AccessTokenCacheTests()
{
- mockCredential = new Mock();
- tokenRequestContext = new TokenRequestContext(new[] { "https://durabletask.azure.com/.default" });
- margin = TimeSpan.FromMinutes(5);
- cancellationToken = CancellationToken.None;
+ this.mockCredential = new Mock();
+ this.tokenRequestContext = new TokenRequestContext(new[] { "https://durabletask.azure.com/.default" });
+ this.margin = TimeSpan.FromMinutes(5);
+ this.cancellationToken = CancellationToken.None;
}
[Fact]
public async Task GetTokenAsync_WhenCalled_ShouldReturnToken()
{
// Arrange
- var expectedToken = new AccessToken("test-token", DateTimeOffset.UtcNow.AddHours(1));
- mockCredential.Setup(c => c.GetTokenAsync(It.IsAny(), It.IsAny()))
+ AccessToken expectedToken = new AccessToken("test-token", DateTimeOffset.UtcNow.AddHours(1));
+ this.mockCredential.Setup(c => c.GetTokenAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(expectedToken);
- var cache = new AccessTokenCache(mockCredential.Object, tokenRequestContext, margin);
+ AccessTokenCache cache = new AccessTokenCache(this.mockCredential.Object, this.tokenRequestContext, this.margin);
// Act
- var token = await cache.GetTokenAsync(cancellationToken);
+ AccessToken token = await cache.GetTokenAsync(this.cancellationToken);
// Assert
token.Should().Be(expectedToken);
- mockCredential.Verify(c => c.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Once);
+ this.mockCredential.Verify(c => c.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Once);
}
[Fact]
public async Task GetTokenAsync_WhenTokenExpired_ShouldRequestNewToken()
{
// Arrange
- var expiredToken = new AccessToken("expired-token", DateTimeOffset.UtcNow.AddMinutes(-5));
- var newToken = new AccessToken("new-token", DateTimeOffset.UtcNow.AddHours(1));
- var cache = new AccessTokenCache(mockCredential.Object, tokenRequestContext, margin);
+ AccessToken expiredToken = new AccessToken("expired-token", DateTimeOffset.UtcNow.AddMinutes(-5));
+ AccessToken newToken = new AccessToken("new-token", DateTimeOffset.UtcNow.AddHours(1));
+ AccessTokenCache cache = new AccessTokenCache(this.mockCredential.Object, this.tokenRequestContext, this.margin);
- mockCredential.SetupSequence(c => c.GetTokenAsync(It.IsAny(), It.IsAny()))
+ this.mockCredential.SetupSequence(c => c.GetTokenAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(expiredToken)
.ReturnsAsync(newToken);
// Act
- var firstToken = await cache.GetTokenAsync(cancellationToken);
- var secondToken = await cache.GetTokenAsync(cancellationToken);
+ AccessToken firstToken = await cache.GetTokenAsync(this.cancellationToken);
+ AccessToken secondToken = await cache.GetTokenAsync(this.cancellationToken);
// Assert
firstToken.Should().Be(expiredToken);
secondToken.Should().Be(newToken);
- mockCredential.Verify(c => c.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Exactly(2));
+ this.mockCredential.Verify(c => c.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Exactly(2));
}
[Fact]
public async Task GetTokenAsync_WhenTokenValid_ShouldReturnCachedToken()
{
// Arrange
- var validToken = new AccessToken("valid-token", DateTimeOffset.UtcNow.AddHours(1));
- mockCredential.Setup(c => c.GetTokenAsync(It.IsAny(), It.IsAny()))
+ AccessToken validToken = new AccessToken("valid-token", DateTimeOffset.UtcNow.AddHours(1));
+ this.mockCredential.Setup(c => c.GetTokenAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(validToken);
- var cache = new AccessTokenCache(mockCredential.Object, tokenRequestContext, margin);
+ AccessTokenCache cache = new AccessTokenCache(this.mockCredential.Object, this.tokenRequestContext, this.margin);
// Act
- var firstToken = await cache.GetTokenAsync(cancellationToken);
- var secondToken = await cache.GetTokenAsync(cancellationToken);
+ AccessToken firstToken = await cache.GetTokenAsync(this.cancellationToken);
+ AccessToken secondToken = await cache.GetTokenAsync(this.cancellationToken);
// Assert
firstToken.Should().Be(validToken);
secondToken.Should().Be(validToken);
- mockCredential.Verify(c => c.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Once);
+ this.mockCredential.Verify(c => c.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Once);
}
[Fact]
public async Task Constructor_WithNullCredential_ShouldThrowNullReferenceException()
{
// Arrange
- var cache = new AccessTokenCache(null!, tokenRequestContext, margin);
+ AccessTokenCache cache = new AccessTokenCache(null!, this.tokenRequestContext, this.margin);
// Act & Assert
// TODO: The constructor should validate its parameters and throw ArgumentNullException,
// but currently it allows null parameters and throws NullReferenceException when used.
- Func action = () => cache.GetTokenAsync(cancellationToken);
+ Func action = () => cache.GetTokenAsync(this.cancellationToken);
await action.Should().ThrowAsync();
}
@@ -101,22 +98,22 @@ public async Task Constructor_WithNullCredential_ShouldThrowNullReferenceExcepti
public async Task GetTokenAsync_WhenTokenNearExpiry_ShouldRequestNewToken()
{
// Arrange
- var expiryTime = DateTimeOffset.UtcNow.AddMinutes(10);
- var nearExpiryToken = new AccessToken("near-expiry-token", expiryTime);
- var newToken = new AccessToken("new-token", expiryTime.AddHours(1));
- var cache = new AccessTokenCache(mockCredential.Object, tokenRequestContext, TimeSpan.FromMinutes(15));
+ DateTimeOffset expiryTime = DateTimeOffset.UtcNow.AddMinutes(10);
+ AccessToken nearExpiryToken = new AccessToken("near-expiry-token", expiryTime);
+ AccessToken newToken = new AccessToken("new-token", expiryTime.AddHours(1));
+ AccessTokenCache cache = new AccessTokenCache(this.mockCredential.Object, this.tokenRequestContext, TimeSpan.FromMinutes(15));
- mockCredential.SetupSequence(c => c.GetTokenAsync(It.IsAny(), It.IsAny()))
+ this.mockCredential.SetupSequence(c => c.GetTokenAsync(It.IsAny(), It.IsAny()))
.ReturnsAsync(nearExpiryToken)
.ReturnsAsync(newToken);
// Act
- var firstToken = await cache.GetTokenAsync(cancellationToken);
- var secondToken = await cache.GetTokenAsync(cancellationToken);
+ AccessToken firstToken = await cache.GetTokenAsync(this.cancellationToken);
+ AccessToken secondToken = await cache.GetTokenAsync(this.cancellationToken);
// Assert
firstToken.Should().Be(nearExpiryToken);
secondToken.Should().Be(newToken);
- mockCredential.Verify(c => c.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Exactly(2));
+ this.mockCredential.Verify(c => c.GetTokenAsync(It.IsAny(), It.IsAny()), Times.Exactly(2));
}
}
diff --git a/test/Shared/AzureManaged.Tests/DurableTaskSchedulerConnectionStringTests.cs b/test/Shared/AzureManaged.Tests/DurableTaskSchedulerConnectionStringTests.cs
index bf5631f5..e70f3e6f 100644
--- a/test/Shared/AzureManaged.Tests/DurableTaskSchedulerConnectionStringTests.cs
+++ b/test/Shared/AzureManaged.Tests/DurableTaskSchedulerConnectionStringTests.cs
@@ -2,17 +2,16 @@
// Licensed under the MIT License.
using FluentAssertions;
-using System.Data.Common;
using Xunit;
namespace Microsoft.DurableTask.Shared.AzureManaged.Tests;
public class DurableTaskSchedulerConnectionStringTests
{
- private const string ValidEndpoint = "myaccount.westus3.durabletask.io";
- private const string ValidTaskHub = "testhub";
- private const string ValidClientId = "00000000-0000-0000-0000-000000000000";
- private const string ValidTenantId = "11111111-1111-1111-1111-111111111111";
+ const string ValidEndpoint = "myaccount.westus3.durabletask.io";
+ const string ValidTaskHub = "testhub";
+ const string ValidClientId = "00000000-0000-0000-0000-000000000000";
+ const string ValidTenantId = "11111111-1111-1111-1111-111111111111";
[Fact]
public void Constructor_WithValidConnectionString_ShouldParseCorrectly()
@@ -21,7 +20,7 @@ public void Constructor_WithValidConnectionString_ShouldParseCorrectly()
string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
// Act
- var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+ DurableTaskSchedulerConnectionString parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
// Assert
parsedConnectionString.Endpoint.Should().Be(ValidEndpoint);
@@ -36,7 +35,7 @@ public void Constructor_WithManagedIdentity_ShouldParseClientId()
string connectionString = $"Endpoint={ValidEndpoint};Authentication=ManagedIdentity;ClientID={ValidClientId};TaskHub={ValidTaskHub}";
// Act
- var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+ DurableTaskSchedulerConnectionString parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
// Assert
parsedConnectionString.ClientId.Should().Be(ValidClientId);
@@ -49,7 +48,7 @@ public void Constructor_WithWorkloadIdentity_ShouldParseAllProperties()
string connectionString = $"Endpoint={ValidEndpoint};Authentication=WorkloadIdentity;ClientID={ValidClientId};TenantId={ValidTenantId};TaskHub={ValidTaskHub}";
// Act
- var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+ DurableTaskSchedulerConnectionString parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
// Assert
parsedConnectionString.ClientId.Should().Be(ValidClientId);
@@ -64,7 +63,7 @@ public void Constructor_WithAdditionallyAllowedTenants_ShouldParseTenantList()
string connectionString = $"Endpoint={ValidEndpoint};Authentication=WorkloadIdentity;AdditionallyAllowedTenants={tenants};TaskHub={ValidTaskHub}";
// Act
- var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+ DurableTaskSchedulerConnectionString parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
// Assert
parsedConnectionString.AdditionallyAllowedTenants.Should().NotBeNull();
@@ -79,7 +78,7 @@ public void Constructor_WithMultipleAdditionallyAllowedTenants_ShouldParseCorrec
"AdditionallyAllowedTenants=tenant1,tenant2,tenant3";
// Act
- var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+ DurableTaskSchedulerConnectionString parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
// Assert
parsedConnectionString.AdditionallyAllowedTenants.Should().NotBeNull();
@@ -95,7 +94,7 @@ public void Constructor_WithCaseInsensitivePropertyNames_ShouldParseCorrectly()
$"clientid={ValidClientId};tenantid={ValidTenantId}";
// Act
- var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+ DurableTaskSchedulerConnectionString parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
// Assert
parsedConnectionString.Endpoint.Should().Be(ValidEndpoint);
@@ -109,10 +108,10 @@ public void Constructor_WithCaseInsensitivePropertyNames_ShouldParseCorrectly()
public void Constructor_WithInvalidConnectionStringFormat_ShouldThrowFormatException()
{
// Arrange
- var connectionString = "This is not a valid=connection string format";
+ string connectionString = "This is not a valid=connection string format";
// Act & Assert
- var action = () => _ = new DurableTaskSchedulerConnectionString(connectionString).Endpoint;
+ Action action = () => _ = new DurableTaskSchedulerConnectionString(connectionString).Endpoint;
action.Should().Throw()
.WithMessage("Value cannot be null. (Parameter 'The connection string is missing the required 'Endpoint' property.')");
}
@@ -121,10 +120,10 @@ public void Constructor_WithInvalidConnectionStringFormat_ShouldThrowFormatExcep
public void Constructor_WithEmptyConnectionString_ShouldThrowArgumentNullException()
{
// Arrange
- var connectionString = string.Empty;
+ string connectionString = string.Empty;
// Act & Assert
- var action = () => _ = new DurableTaskSchedulerConnectionString(connectionString).Endpoint;
+ Action action = () => _ = new DurableTaskSchedulerConnectionString(connectionString).Endpoint;
action.Should().Throw()
.WithMessage("Value cannot be null. (Parameter 'The connection string is missing the required 'Endpoint' property.')");
}
@@ -136,7 +135,7 @@ public void Constructor_WithDuplicateKeys_ShouldUseLastValue()
string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub=hub1;TaskHub=hub2";
// Act
- var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+ DurableTaskSchedulerConnectionString parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
// Assert
parsedConnectionString.TaskHubName.Should().Be("hub2");
@@ -149,7 +148,7 @@ public void Constructor_WithMissingRequiredProperties_ShouldThrowArgumentNullExc
string connectionString = $"Authentication=DefaultAzure;TaskHub={ValidTaskHub}"; // Missing Endpoint
// Act & Assert
- var action = () => _ = new DurableTaskSchedulerConnectionString(connectionString).Endpoint;
+ Action action = () => _ = new DurableTaskSchedulerConnectionString(connectionString).Endpoint;
action.Should().Throw()
.WithMessage("*'Endpoint' property*");
}
@@ -161,7 +160,7 @@ public void Constructor_WithInvalidConnectionString_ShouldThrowArgumentException
string connectionString = "This is not a valid connection string";
// Act & Assert
- var action = () => new DurableTaskSchedulerConnectionString(connectionString);
+ Action action = () => new DurableTaskSchedulerConnectionString(connectionString);
action.Should().Throw()
.WithMessage("*Format of the initialization string does not conform to specification*");
}
@@ -172,7 +171,7 @@ public void Constructor_WithInvalidConnectionString_ShouldThrowArgumentException
public void Constructor_WithNullOrEmptyConnectionString_ShouldThrowArgumentNullException(string? connectionString)
{
// Act & Assert
- var action = () => _ = new DurableTaskSchedulerConnectionString(connectionString!).Endpoint;
+ Action action = () => _ = new DurableTaskSchedulerConnectionString(connectionString!).Endpoint;
action.Should().Throw();
}
@@ -181,7 +180,7 @@ public void GetValue_WithNonExistentProperty_ShouldReturnNull()
{
// Arrange
string connectionString = $"Endpoint={ValidEndpoint};Authentication=DefaultAzure;TaskHub={ValidTaskHub}";
- var parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
+ DurableTaskSchedulerConnectionString parsedConnectionString = new DurableTaskSchedulerConnectionString(connectionString);
// Assert
parsedConnectionString.ClientId.Should().BeNull();
diff --git a/test/Shared/AzureManaged.Tests/Shared.AzureManaged.Tests.csproj b/test/Shared/AzureManaged.Tests/Shared.AzureManaged.Tests.csproj
index 35864cba..a30522af 100644
--- a/test/Shared/AzureManaged.Tests/Shared.AzureManaged.Tests.csproj
+++ b/test/Shared/AzureManaged.Tests/Shared.AzureManaged.Tests.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net6.0
diff --git a/test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerExtensionsTests.cs b/test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerExtensionsTests.cs
index 1ce3d803..761cbcde 100644
--- a/test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerExtensionsTests.cs
+++ b/test/Worker/AzureManaged.Tests/DurableTaskSchedulerWorkerExtensionsTests.cs
@@ -4,37 +4,34 @@
using Azure.Core;
using Azure.Identity;
using FluentAssertions;
-using Grpc.Net.Client;
-using Microsoft.DurableTask.Worker;
using Microsoft.DurableTask.Worker.Grpc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
-using System.ComponentModel.DataAnnotations;
using Xunit;
namespace Microsoft.DurableTask.Worker.AzureManaged.Tests;
public class DurableTaskSchedulerWorkerExtensionsTests
{
- private const string ValidEndpoint = "myaccount.westus3.durabletask.io";
- private const string ValidTaskHub = "testhub";
+ const string ValidEndpoint = "myaccount.westus3.durabletask.io";
+ const string ValidTaskHub = "testhub";
[Fact]
public void UseDurableTaskScheduler_WithEndpointAndCredential_ShouldConfigureCorrectly()
{
// Arrange
- var services = new ServiceCollection();
- var mockBuilder = new Mock();
+ ServiceCollection services = new ServiceCollection();
+ Mock mockBuilder = new Mock();
mockBuilder.Setup(b => b.Services).Returns(services);
- var credential = new DefaultAzureCredential();
+ DefaultAzureCredential credential = new DefaultAzureCredential();
// Act
mockBuilder.Object.UseDurableTaskScheduler(ValidEndpoint, ValidTaskHub, credential);
// Assert
- var provider = services.BuildServiceProvider();
- var options = provider.GetService>();
+ ServiceProvider provider = services.BuildServiceProvider();
+ IOptions