Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom connection string names when creating a DurableClient in an ASP.NET Core app #1895

Merged
merged 5 commits into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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);
Expand All @@ -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);
}

Expand All @@ -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
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions test/Common/DurableTaskEndToEndTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -219,6 +225,48 @@ public async Task DurableClient_AzureStorage_SuccessfulSetup()
}
}

#if !FUNCTIONS_V1
/// <summary>
/// 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)).
/// </summary>
[Fact]
[Trait("Category", PlatformSpecificHelpers.TestCategory)]
public async Task DurableClient_AzureStorage__ReadsCustomStorageConnString()
{
string taskHubName = TestHelpers.GetTaskHubNameFromTestName(
nameof(this.DurableClient_AzureStorage__ReadsCustomStorageConnString),
enableExtendedSessions: false);

Dictionary<string, string> appSettings = new Dictionary<string, string>
{
{ "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

/// <summary>
/// End-to-end test which validates a simple orchestrator function does not have assigned value for <see cref="DurableOrchestrationContext.ParentInstanceId"/>.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions test/Common/DurableTaskHostExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public static async Task<TestDurableClient> 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<string, object>
{
Expand Down
19 changes: 18 additions & 1 deletion test/Common/TestHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<IDurabilityProviderFactory> durabilityProviderFactories = null)
Expand Down
15 changes: 15 additions & 0 deletions test/FunctionsV2/PlatformSpecificHelpers.FunctionsV2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<DurableTaskOptions> options,
IEnumerable<IDurabilityProviderFactory> durabilityProviderFactories)
Expand Down
30 changes: 30 additions & 0 deletions test/FunctionsV2/TestCustomConnectionsStringResolver.cs
Original file line number Diff line number Diff line change
@@ -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
ConnorMcMahon marked this conversation as resolved.
Show resolved Hide resolved
{
internal class TestCustomConnectionsStringResolver : IConnectionStringResolver
{
private readonly Dictionary<string, string> connectionStrings;

public TestCustomConnectionsStringResolver(Dictionary<string, string> connectionStrings)
{
this.connectionStrings = connectionStrings;
}

public string Resolve(string connectionStringName)
{
if (this.connectionStrings.TryGetValue(connectionStringName, out string value))
{
return value;
}

return null;
}
}
}