diff --git a/release_notes.md b/release_notes.md
index 87ce7dbb7..68b06ba95 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -3,5 +3,6 @@
## Bug fixes
* Fix message loss bug in ContinueAsNew scenarios ([Azure/durabletask#544](https://github.com/Azure/durabletask/pull/544))
+* Allow custom connection string names when creating a DurableClient in an ASP.NET Core app (external app) (#1895)
## Breaking Changes
\ No newline at end of file
diff --git a/src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProviderFactory.cs b/src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProviderFactory.cs
index a6f03cc48..d4be166e9 100644
--- a/src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProviderFactory.cs
+++ b/src/WebJobs.Extensions.DurableTask/AzureStorageDurabilityProviderFactory.cs
@@ -83,7 +83,7 @@ internal string GetDefaultStorageConnectionString()
// This method should not be called before the app settings are resolved into the options.
// Because of this, we wait to validate the options until right before building a durability provider, rather
// than in the Factory constructor.
- private void EnsureInitialized()
+ private void EnsureDefaultClientSettingsInitialized()
{
if (!this.hasValidatedOptions)
{
@@ -103,7 +103,7 @@ private void EnsureInitialized()
public virtual DurabilityProvider GetDurabilityProvider()
{
- this.EnsureInitialized();
+ this.EnsureDefaultClientSettingsInitialized();
if (this.defaultStorageProvider == null)
{
var defaultService = new AzureStorageOrchestrationService(this.defaultSettings);
@@ -120,7 +120,11 @@ public virtual DurabilityProvider GetDurabilityProvider()
public virtual DurabilityProvider GetDurabilityProvider(DurableClientAttribute attribute)
{
- this.EnsureInitialized();
+ if (!attribute.ExternalClient)
+ {
+ this.EnsureDefaultClientSettingsInitialized();
+ }
+
return this.GetAzureStorageStorageProvider(attribute);
}
@@ -130,8 +134,12 @@ private AzureStorageDurabilityProvider GetAzureStorageStorageProvider(DurableCli
AzureStorageOrchestrationServiceSettings settings = this.GetAzureStorageOrchestrationServiceSettings(connectionName, attribute.TaskHub);
AzureStorageDurabilityProvider innerClient;
- if (string.Equals(this.defaultSettings.TaskHubName, settings.TaskHubName, StringComparison.OrdinalIgnoreCase) &&
- string.Equals(this.defaultSettings.StorageConnectionString, settings.StorageConnectionString, StringComparison.OrdinalIgnoreCase))
+
+ // Need to check this.defaultStorageProvider != null for external clients that call GetDurabilityProvider(attribute)
+ // which never initializes the defaultStorageProvider.
+ if (string.Equals(this.defaultSettings?.TaskHubName, settings.TaskHubName, StringComparison.OrdinalIgnoreCase) &&
+ string.Equals(this.defaultSettings?.StorageConnectionString, settings.StorageConnectionString, StringComparison.OrdinalIgnoreCase) &&
+ this.defaultStorageProvider != null)
{
// It's important that clients use the same AzureStorageOrchestrationService instance
// as the host when possible to ensure we any send operations can be picked up
diff --git a/src/WebJobs.Extensions.DurableTask/Microsoft.Azure.WebJobs.Extensions.DurableTask.xml b/src/WebJobs.Extensions.DurableTask/Microsoft.Azure.WebJobs.Extensions.DurableTask.xml
index acee3d77b..842705265 100644
--- a/src/WebJobs.Extensions.DurableTask/Microsoft.Azure.WebJobs.Extensions.DurableTask.xml
+++ b/src/WebJobs.Extensions.DurableTask/Microsoft.Azure.WebJobs.Extensions.DurableTask.xml
@@ -2508,6 +2508,14 @@
Populate default configurations of to create Durable Clients.
Returns the provided .
+
+
+ Adds the Durable Task extension to the provided .
+
+ The to configure.
+ Populate configurations of to create Durable Clients.
+ Returns the provided .
+
Adds the Durable Task extension to the provided .
diff --git a/test/Common/DurableTaskEndToEndTests.cs b/test/Common/DurableTaskEndToEndTests.cs
index bc6bb2d35..f3659472f 100644
--- a/test/Common/DurableTaskEndToEndTests.cs
+++ b/test/Common/DurableTaskEndToEndTests.cs
@@ -15,9 +15,15 @@
using System.Threading.Tasks;
using DurableTask.AzureStorage;
using DurableTask.Core;
+using Microsoft.Azure.WebJobs.Extensions.DurableTask.ContextImplementations;
+using Microsoft.Azure.WebJobs.Extensions.DurableTask.Options;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Diagnostics.Tracing;
+#if !FUNCTIONS_V1
+using Microsoft.Extensions.Hosting;
+using WebJobs.Extensions.DurableTask.Tests.V2;
+#endif
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Moq;
@@ -219,6 +225,48 @@ public async Task DurableClient_AzureStorage_SuccessfulSetup()
}
}
+#if !FUNCTIONS_V1
+ ///
+ /// End to end test that ensures that customers can configure custom connection string names
+ /// using DurableClientOptions when they create a DurableClient from an external app (e.g. ASP.NET Core app).
+ /// The appSettings dictionary acts like appsettings.json and durableClientOptions are the
+ /// settings passed in during a call to DurableClient (IDurableClientFactory.CreateClient(durableClientOptions)).
+ ///
+ [Fact]
+ [Trait("Category", PlatformSpecificHelpers.TestCategory)]
+ public async Task DurableClient_AzureStorage__ReadsCustomStorageConnString()
+ {
+ string taskHubName = TestHelpers.GetTaskHubNameFromTestName(
+ nameof(this.DurableClient_AzureStorage__ReadsCustomStorageConnString),
+ enableExtendedSessions: false);
+
+ Dictionary appSettings = new Dictionary
+ {
+ { "CustomStorageAccountName", TestHelpers.GetStorageConnectionString() },
+ { "TestTaskHub", taskHubName },
+ };
+
+ // ConnectionName is used to look up the storage connection string in appsettings
+ DurableClientOptions durableClientOptions = new DurableClientOptions
+ {
+ ConnectionName = "CustomStorageAccountName",
+ TaskHub = taskHubName,
+ };
+
+ var connectionStringResolver = new TestCustomConnectionsStringResolver(appSettings);
+
+ using (IHost clientHost = TestHelpers.GetJobHostExternalEnvironment(
+ connectionStringResolver: connectionStringResolver))
+ {
+ await clientHost.StartAsync();
+ IDurableClientFactory durableClientFactory = clientHost.Services.GetService(typeof(IDurableClientFactory)) as DurableClientFactory;
+ IDurableClient durableClient = durableClientFactory.CreateClient(durableClientOptions);
+ Assert.Equal(taskHubName, durableClient.TaskHubName);
+ await clientHost.StopAsync();
+ }
+ }
+#endif
+
///
/// End-to-end test which validates a simple orchestrator function does not have assigned value for .
///
diff --git a/test/Common/DurableTaskHostExtensions.cs b/test/Common/DurableTaskHostExtensions.cs
index 40a4fdc63..969f02e71 100644
--- a/test/Common/DurableTaskHostExtensions.cs
+++ b/test/Common/DurableTaskHostExtensions.cs
@@ -20,6 +20,7 @@ public static async Task StartOrchestratorAsync(
var startFunction = useTaskHubFromAppSettings ?
typeof(ClientFunctions).GetMethod(nameof(ClientFunctions.StartFunctionWithTaskHub)) :
typeof(ClientFunctions).GetMethod(nameof(ClientFunctions.StartFunction));
+
var clientRef = new TestDurableClient[1];
var args = new Dictionary
{
diff --git a/test/Common/TestHelpers.cs b/test/Common/TestHelpers.cs
index a2fc1e757..75c0769f5 100644
--- a/test/Common/TestHelpers.cs
+++ b/test/Common/TestHelpers.cs
@@ -12,8 +12,10 @@
using DurableTask.AzureStorage;
using Microsoft.ApplicationInsights.Channel;
#if !FUNCTIONS_V1
-using Microsoft.Azure.WebJobs.Extensions.DurableTask.Correlation;
+using Microsoft.Extensions.Hosting;
#endif
+using Microsoft.Azure.WebJobs.Extensions.DurableTask.ContextImplementations;
+using Microsoft.Azure.WebJobs.Extensions.DurableTask.Options;
using Microsoft.Azure.WebJobs.Host.TestCommon;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -200,6 +202,21 @@ public static ITestHost GetJobHostWithOptions(
}
#if !FUNCTIONS_V1
+ public static IHost GetJobHostExternalEnvironment(IConnectionStringResolver connectionStringResolver = null)
+ {
+ if (connectionStringResolver == null)
+ {
+ connectionStringResolver = new TestConnectionStringResolver();
+ }
+
+ return GetJobHostWithOptionsForDurableClientFactoryExternal(connectionStringResolver);
+ }
+
+ public static IHost GetJobHostWithOptionsForDurableClientFactoryExternal(IConnectionStringResolver connectionStringResolver)
+ {
+ return PlatformSpecificHelpers.CreateJobHostExternalEnvironment(connectionStringResolver);
+ }
+
public static ITestHost GetJobHostWithMultipleDurabilityProviders(
DurableTaskOptions options = null,
IEnumerable durabilityProviderFactories = null)
diff --git a/test/FunctionsV2/PlatformSpecificHelpers.FunctionsV2.cs b/test/FunctionsV2/PlatformSpecificHelpers.FunctionsV2.cs
index a67e5f354..e31bb2069 100644
--- a/test/FunctionsV2/PlatformSpecificHelpers.FunctionsV2.cs
+++ b/test/FunctionsV2/PlatformSpecificHelpers.FunctionsV2.cs
@@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.Azure.WebJobs.Extensions.DurableTask.Correlation;
+using Microsoft.Azure.WebJobs.Extensions.DurableTask.Options;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
@@ -102,6 +103,20 @@ public static ITestHost CreateJobHost(
return new FunctionsV2HostWrapper(host, options, nameResolver);
}
+ public static IHost CreateJobHostExternalEnvironment(IConnectionStringResolver connectionStringResolver)
+ {
+ IHost host = new HostBuilder()
+ .ConfigureServices(
+ serviceCollection =>
+ {
+ serviceCollection.AddSingleton(connectionStringResolver);
+ serviceCollection.AddDurableClientFactory();
+ })
+ .Build();
+
+ return host;
+ }
+
public static ITestHost CreateJobHostWithMultipleDurabilityProviders(
IOptions options,
IEnumerable durabilityProviderFactories)
diff --git a/test/FunctionsV2/TestCustomConnectionsStringResolver.cs b/test/FunctionsV2/TestCustomConnectionsStringResolver.cs
new file mode 100644
index 000000000..49aa0dfc7
--- /dev/null
+++ b/test/FunctionsV2/TestCustomConnectionsStringResolver.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.Azure.WebJobs.Extensions.DurableTask;
+
+namespace WebJobs.Extensions.DurableTask.Tests.V2
+{
+ internal class TestCustomConnectionsStringResolver : IConnectionStringResolver
+ {
+ private readonly Dictionary connectionStrings;
+
+ public TestCustomConnectionsStringResolver(Dictionary connectionStrings)
+ {
+ this.connectionStrings = connectionStrings;
+ }
+
+ public string Resolve(string connectionStringName)
+ {
+ if (this.connectionStrings.TryGetValue(connectionStringName, out string value))
+ {
+ return value;
+ }
+
+ return null;
+ }
+ }
+}