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

Implement CosmosDBOptions to allow configuration of the CosmosDB service client via CosmosClientOptions #2483

Merged
merged 9 commits into from
Jun 12, 2024
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
19 changes: 15 additions & 4 deletions extensions/Worker.Extensions.CosmosDB/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@
- My change description (#PR/#issue)
-->

### Microsoft.Azure.Functions.Worker.Extensions.CosmosDB 4.8.1
### Microsoft.Azure.Functions.Worker.Extensions.CosmosDB 4.9.0

- Updating `Microsoft.Azure.WebJobs.Extensions.CosmosDB` reference to 4.6.1
- Updating `Microsoft.Extensions.Azure` reference to 1.7.3
- Updating `Microsoft.Azure.Cosmos` reference to 3.39.1
- Implement `CosmosDBExtensionOptions` to allow configuration of the CosmosDB service client via `CosmosClientOptions` (#2483)

#### Example Usage

```csharp
.ConfigureFunctionsWorkerDefaults((builder) =>
{
builder.ConfigureCosmosDBExtensionOptions((options) =>
{
options.ClientOptions.ConnectionMode = ConnectionMode.Direct;
options.ClientOptions.ApplicationName = "MyApp";
});
})
```
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@

namespace Microsoft.Azure.Functions.Worker
{
/// <summary>
/// Internal options for configuring the CosmosDB binding.
/// This class is used internally by the Azure Functions runtime to manage the CosmosDB connection and clients.
/// It is not intended to be used directly in user code.
/// Any public configuration options should be set on the <see cref="CosmosDBExtensionOptions"/> class, which is publicly accessible.
/// </summary>
internal class CosmosDBBindingOptions
{
public string? ConnectionName { get; set; }
Expand All @@ -19,7 +25,7 @@ internal class CosmosDBBindingOptions

public TokenCredential? Credential { get; set; }

public CosmosSerializer? Serializer { get; set; }
public CosmosDBExtensionOptions? CosmosExtensionOptions { get; set; }

internal string BuildCacheKey(string connection, string region) => $"{connection}|{region}";

Expand All @@ -32,24 +38,23 @@ internal virtual CosmosClient GetClient(string preferredLocations = "")
throw new ArgumentNullException(nameof(ConnectionName));
}

string cacheKey = BuildCacheKey(ConnectionName!, preferredLocations);

CosmosClientOptions cosmosClientOptions = new ()
{
ConnectionMode = ConnectionMode.Gateway
};

if (!string.IsNullOrEmpty(preferredLocations))
if (CosmosExtensionOptions is null)
{
cosmosClientOptions.ApplicationPreferredRegions = Utilities.ParsePreferredLocations(preferredLocations);
CosmosExtensionOptions = new CosmosDBExtensionOptions();
}

if (Serializer is not null)
string cacheKey = BuildCacheKey(ConnectionName!, preferredLocations);

// Do not override if preferred locations is configured via CosmosClientOptions
if (!string.IsNullOrEmpty(preferredLocations) && CosmosExtensionOptions.ClientOptions.ApplicationPreferredRegions is null)
{
cosmosClientOptions.Serializer = Serializer;
// Clone to avoid modifying the original options
CosmosClientOptions cosmosClientOptions = CosmosExtensionOptions.ClientOptions.MemberwiseClone<CosmosClientOptions>();
cosmosClientOptions.ApplicationPreferredRegions = Utilities.ParsePreferredLocations(preferredLocations);
return ClientCache.GetOrAdd(cacheKey, (c) => CreateService(cosmosClientOptions));
}

return ClientCache.GetOrAdd(cacheKey, (c) => CreateService(cosmosClientOptions));
return ClientCache.GetOrAdd(cacheKey, (c) => CreateService(CosmosExtensionOptions.ClientOptions));
}

private CosmosClient CreateService(CosmosClientOptions cosmosClientOptions)
Expand All @@ -59,4 +64,4 @@ private CosmosClient CreateService(CosmosClientOptions cosmosClientOptions)
: new CosmosClient(ConnectionString, cosmosClientOptions); // Connection string based auth
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Functions.Worker.Extensions;
using Microsoft.Azure.Functions.Worker.Extensions.CosmosDB;
using Microsoft.Extensions.Azure;
Expand All @@ -15,12 +16,14 @@ internal class CosmosDBBindingOptionsSetup : IConfigureNamedOptions<CosmosDBBind
private readonly IConfiguration _configuration;
private readonly AzureComponentFactory _componentFactory;
private readonly IOptionsMonitor<WorkerOptions> _workerOptions;
private readonly IOptionsMonitor<CosmosDBExtensionOptions> _cosmosExtensionOptions;

public CosmosDBBindingOptionsSetup(IConfiguration configuration, AzureComponentFactory componentFactory, IOptionsMonitor<WorkerOptions> workerOptions)
public CosmosDBBindingOptionsSetup(IConfiguration configuration, AzureComponentFactory componentFactory, IOptionsMonitor<WorkerOptions> workerOptions, IOptionsMonitor<CosmosDBExtensionOptions> cosmosExtensionOptions)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_componentFactory = componentFactory ?? throw new ArgumentNullException(nameof(componentFactory));
_workerOptions = workerOptions ?? throw new ArgumentNullException(nameof(workerOptions));
_cosmosExtensionOptions = cosmosExtensionOptions ?? throw new ArgumentNullException(nameof(cosmosExtensionOptions));
}

public void Configure(CosmosDBBindingOptions options)
Expand Down Expand Up @@ -56,7 +59,7 @@ public void Configure(string connectionName, CosmosDBBindingOptions options)
options.Credential = _componentFactory.CreateTokenCredential(connectionSection);
}

options.Serializer = new WorkerCosmosSerializer(_workerOptions.CurrentValue.Serializer);
options.CosmosExtensionOptions = _cosmosExtensionOptions.CurrentValue;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Azure.Cosmos;

namespace Microsoft.Azure.Functions.Worker
{
public class CosmosDBExtensionOptions
{
/// <summary>
/// Gets or sets the CosmosClientOptions.
/// </summary>
public CosmosClientOptions ClientOptions { get; set; } = new() { ConnectionMode = ConnectionMode.Gateway };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Reflection;
using Microsoft.Azure.Cosmos;

namespace Microsoft.Azure.Functions.Worker
{
internal static class CloningExtensions
{
public static T MemberwiseClone<T>(this T original)
{
if (original is null)
{
throw new ArgumentNullException(nameof(original));
}

// Get the type of the object
Type type = original.GetType();

// Get the internal Clone method
MethodInfo cloneMethod = type.GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);

if (cloneMethod is null)
{
throw new InvalidOperationException("The MemberwiseClone method could not be found.");
}

// Invoke the Clone method
return (T)cloneMethod.Invoke(original, null);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand All @@ -27,9 +29,30 @@ public static IFunctionsWorkerApplicationBuilder ConfigureCosmosDBExtension(this

builder.Services.AddAzureClientsCore(); // Adds AzureComponentFactory
builder.Services.AddOptions<CosmosDBBindingOptions>();
builder.Services.AddOptions<CosmosDBExtensionOptions>()
.Configure<IOptions<WorkerOptions>>((cosmos, worker) =>
{
if (cosmos.ClientOptions.Serializer is null)
{
cosmos.ClientOptions.Serializer = new WorkerCosmosSerializer(worker.Value.Serializer);
}
});

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CosmosDBBindingOptions>, CosmosDBBindingOptionsSetup>());

return builder;
}

/// <summary>
/// Configures the CosmosDBExtensionOptions for the Functions Worker Cosmos extension.
/// </summary>
/// <param name="builder">The IFunctionsWorkerApplicationBuilder to add the configuration to.</param>
/// <param name="options">An Action to configure the CosmosDBExtensionOptions.</param>
/// <returns>The same instance of the <see cref="IFunctionsWorkerApplicationBuilder"/> for chaining.</returns>
public static IFunctionsWorkerApplicationBuilder ConfigureCosmosDBExtensionOptions(this IFunctionsWorkerApplicationBuilder builder, Action<CosmosDBExtensionOptions> options)
{
builder.Services.Configure(options);
return builder;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<Description>Azure Cosmos DB extensions for .NET isolated functions</Description>

<!--Version information-->
<VersionPrefix>4.8.1</VersionPrefix>
<VersionPrefix>4.9.0</VersionPrefix>

<!--Temporarily opting out of documentation. Pending documentation-->
<GenerateDocumentationFile>false</GenerateDocumentationFile>
Expand Down
Loading