From c7d69b35098cc58925fdf4fcd71db51d20657d13 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Fri, 9 Oct 2020 09:48:50 -0700 Subject: [PATCH] Move configuration-based client logic to Microsoft.Azure.WebJobs.Extensions.Clients (#15804) --- .../AzureClientsExtensionConfigProvider.cs | 70 ++++++--- .../AzureClientsWebJobsBuilderExtensions.cs | 3 +- .../Shared/WebJobsConfigurationExtensions.cs} | 8 +- .../tests/WebJobsConfigurationTests.cs | 12 +- ...crosoft.Extensions.Azure.netstandard2.0.cs | 4 +- .../src/AzureClientFactoryBuilder.cs | 13 -- .../AzureClientServiceCollectionExtensions.cs | 5 +- .../src/Internal/AzureClientFactory.cs | 13 +- .../src/Internal/AzureComponentFactory.cs | 11 ++ .../src/Internal/AzureComponentFactoryImpl.cs | 10 ++ .../Internal/FallbackAzureClientFactory.cs | 100 ------------- .../tests/AzureClientFactoryTests.cs | 132 ----------------- .../tests/AzureComponentFactoryTests.cs | 139 ++++++++++++++++++ ...re.WebJobs.Extensions.Storage.Blobs.csproj | 3 +- .../StorageBlobsWebJobsBuilderExtensions.cs | 2 + .../tests/BlobConfigurationTests.cs | 4 +- .../src/Shared/Constants.cs | 5 - .../src/Shared/StorageClientProvider.cs | 1 + ...e.WebJobs.Extensions.Storage.Queues.csproj | 3 +- .../StorageQueuesWebJobsBuilderExtensions.cs | 2 +- sdk/storage/Directory.Build.props | 1 + 21 files changed, 240 insertions(+), 301 deletions(-) rename sdk/{storage/Azure.Storage.Webjobs.Extensions.Common/src/Shared/IConfigurationExtensions.cs => extensions/Microsoft.Azure.WebJobs.Extensions.Clients/src/Shared/WebJobsConfigurationExtensions.cs} (86%) delete mode 100644 sdk/extensions/Microsoft.Extensions.Azure/src/Internal/FallbackAzureClientFactory.cs create mode 100644 sdk/extensions/Microsoft.Extensions.Azure/tests/AzureComponentFactoryTests.cs diff --git a/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/src/AzureClientsExtensionConfigProvider.cs b/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/src/AzureClientsExtensionConfigProvider.cs index 2e0ae31f8daec..3c9e4e7a8ce84 100644 --- a/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/src/AzureClientsExtensionConfigProvider.cs +++ b/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/src/AzureClientsExtensionConfigProvider.cs @@ -2,25 +2,32 @@ // Licensed under the MIT License. using System; +using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Azure.Core; using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.Clients.Shared; using Microsoft.Azure.WebJobs.Host; using Microsoft.Azure.WebJobs.Host.Bindings; using Microsoft.Azure.WebJobs.Host.Config; using Microsoft.Extensions.Azure; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.Hosting { internal class AzureClientsExtensionConfigProvider : IExtensionConfigProvider { - private readonly IServiceProvider _serviceProvider; + private readonly AzureComponentFactory _componentFactory; + private readonly IConfiguration _configuration; private readonly INameResolver _nameResolver; - public AzureClientsExtensionConfigProvider(IServiceProvider serviceProvider, INameResolver nameResolver) + public AzureClientsExtensionConfigProvider(AzureComponentFactory componentFactory, IConfiguration configuration, INameResolver nameResolver) { - _serviceProvider = serviceProvider; + _componentFactory = componentFactory; + _configuration = configuration; _nameResolver = nameResolver; } @@ -32,34 +39,59 @@ public void Initialize(ExtensionConfigContext context) private IValueBinder CreateValueBinder(Type type, AzureClientAttribute attribute) { - return (IValueBinder)Activator.CreateInstance( - typeof(AzureClientValueProvider<>).MakeGenericType(type), - _serviceProvider, - _nameResolver.ResolveWholeString(attribute.Connection)); + var name = _nameResolver.ResolveWholeString(attribute.Connection); + var section = _configuration.GetWebJobsConnectionStringSection(name); + if (!section.Exists()) + { + throw new InvalidOperationException($"Unable to find a configuration section with the name {name} to configure the client with."); + } + + return new AzureClientValueProvider( + type, + _componentFactory, + section); } - private class AzureClientValueProvider : IValueBinder + private class AzureClientValueProvider : IValueBinder { - private readonly IServiceProvider _serviceProvider; - private readonly string _connection; + private readonly Type _clientType; + private readonly AzureComponentFactory _componentFactory; + private readonly IConfigurationSection _configuration; - public AzureClientValueProvider(IServiceProvider serviceProvider, string connection) + public AzureClientValueProvider(Type clientType, AzureComponentFactory componentFactory, IConfigurationSection configuration) { - _serviceProvider = serviceProvider; - _connection = connection; + _clientType = clientType; + _componentFactory = componentFactory; + _configuration = configuration; } public Task GetValueAsync() { - return Task.FromResult( - (object)_serviceProvider - .GetRequiredService>() - .CreateClient(_connection)); + Type clientOptionType = null; + foreach (var constructor in _clientType.GetConstructors(BindingFlags.Public | BindingFlags.Instance)) + { + var lastParameter = constructor.GetParameters().LastOrDefault(); + if (lastParameter != null && typeof(ClientOptions).IsAssignableFrom(lastParameter.ParameterType)) + { + clientOptionType = lastParameter.ParameterType; + break; + } + } + + if (clientOptionType == null) + { + throw new InvalidOperationException("Unable to detect the client option type"); + } + + var credential = _componentFactory.CreateCredential(_configuration); + var options = _componentFactory.CreateClientOptions(clientOptionType, null, _configuration); + + return Task.FromResult(_componentFactory.CreateClient(_clientType, _configuration, credential, options)); } - public string ToInvokeString() => $"{typeof(TClient).Name} Connection: {_connection}"; + public string ToInvokeString() => $"{_clientType.Name} Connection: {_configuration.Path}"; - public Type Type => typeof(TClient); + public Type Type => _clientType; public Task SetValueAsync(object value, CancellationToken cancellationToken) { diff --git a/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/src/AzureClientsWebJobsBuilderExtensions.cs b/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/src/AzureClientsWebJobsBuilderExtensions.cs index b5a6e1e3dd309..44e1d9a5455a2 100644 --- a/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/src/AzureClientsWebJobsBuilderExtensions.cs +++ b/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/src/AzureClientsWebJobsBuilderExtensions.cs @@ -29,8 +29,7 @@ public static IWebJobsBuilder AddAzureClients(this IWebJobsBuilder builder) throw new ArgumentNullException(nameof(builder)); } - builder.Services.AddAzureClients(builder => - builder.UseConfiguration(provider => new WebJobsConfiguration(provider.GetRequiredService()))); + builder.Services.AddAzureClientsCore(); builder.AddExtension(); return builder; diff --git a/sdk/storage/Azure.Storage.Webjobs.Extensions.Common/src/Shared/IConfigurationExtensions.cs b/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/src/Shared/WebJobsConfigurationExtensions.cs similarity index 86% rename from sdk/storage/Azure.Storage.Webjobs.Extensions.Common/src/Shared/IConfigurationExtensions.cs rename to sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/src/Shared/WebJobsConfigurationExtensions.cs index ddfc31cbdf9b1..a028bfa166c98 100644 --- a/sdk/storage/Azure.Storage.Webjobs.Extensions.Common/src/Shared/IConfigurationExtensions.cs +++ b/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/src/Shared/WebJobsConfigurationExtensions.cs @@ -3,10 +3,12 @@ using Microsoft.Extensions.Configuration; -namespace Microsoft.Azure.WebJobs.Extensions.Storage.Common +namespace Microsoft.Azure.WebJobs.Extensions.Clients.Shared { - internal static class IConfigurationExtensions + internal static class WebJobsConfigurationExtensions { + private const string WebJobsConfigurationSectionName = "AzureWebJobs"; + public static IConfigurationSection GetWebJobsConnectionStringSection(this IConfiguration configuration, string connectionStringName) { // first try prefixing @@ -24,7 +26,7 @@ public static IConfigurationSection GetWebJobsConnectionStringSection(this IConf public static string GetPrefixedConnectionStringName(string connectionStringName) { - return Constants.WebJobsConfigurationSectionName + connectionStringName; + return WebJobsConfigurationSectionName + connectionStringName; } /// diff --git a/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/tests/WebJobsConfigurationTests.cs b/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/tests/WebJobsConfigurationTests.cs index d35a4f8abf2f3..4338884637df4 100644 --- a/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/tests/WebJobsConfigurationTests.cs +++ b/sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/tests/WebJobsConfigurationTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using Microsoft.Azure.WebJobs.Extensions.Clients.Shared; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using NUnit.Framework; @@ -20,9 +21,8 @@ public void KeysAreMappedForSingleValue(string actualKeyName) { new KeyValuePair(actualKeyName, "value") }); - var configuration = new WebJobsConfiguration(builder.Build()); - - Assert.AreEqual(configuration.GetSection("Key").Value, "value"); + var configuration = builder.Build(); + Assert.AreEqual(configuration.GetWebJobsConnectionStringSection("Key").Value, "value"); } [TestCase("Key")] @@ -36,10 +36,10 @@ public void KeysAreMappedForSections(string actualKeyName) new KeyValuePair(actualKeyName + ":Value1", "value1"), new KeyValuePair(actualKeyName + ":Value2", "value2") }); - var configuration = new WebJobsConfiguration(builder.Build()); + var configuration = builder.Build(); - Assert.AreEqual(configuration.GetSection("Key")["Value1"], "value1"); - Assert.AreEqual(configuration.GetSection("Key")["Value2"], "value2"); + Assert.AreEqual(configuration.GetWebJobsConnectionStringSection("Key")["Value1"], "value1"); + Assert.AreEqual(configuration.GetWebJobsConnectionStringSection("Key")["Value2"], "value2"); } } } \ No newline at end of file diff --git a/sdk/extensions/Microsoft.Extensions.Azure/api/Microsoft.Extensions.Azure.netstandard2.0.cs b/sdk/extensions/Microsoft.Extensions.Azure/api/Microsoft.Extensions.Azure.netstandard2.0.cs index bbb63b3d7c0d5..93118b3ebe8f0 100644 --- a/sdk/extensions/Microsoft.Extensions.Azure/api/Microsoft.Extensions.Azure.netstandard2.0.cs +++ b/sdk/extensions/Microsoft.Extensions.Azure/api/Microsoft.Extensions.Azure.netstandard2.0.cs @@ -19,18 +19,18 @@ internal AzureClientFactoryBuilder() { } public Microsoft.Extensions.Azure.AzureClientFactoryBuilder ConfigureDefaults(Microsoft.Extensions.Configuration.IConfiguration configuration) { throw null; } public Microsoft.Extensions.Azure.AzureClientFactoryBuilder ConfigureDefaults(System.Action configureOptions) { throw null; } public Microsoft.Extensions.Azure.AzureClientFactoryBuilder ConfigureDefaults(System.Action configureOptions) { throw null; } - public Microsoft.Extensions.Azure.AzureClientFactoryBuilder UseConfiguration(System.Func configurationProvider) { throw null; } public Microsoft.Extensions.Azure.AzureClientFactoryBuilder UseCredential(Azure.Core.TokenCredential tokenCredential) { throw null; } public Microsoft.Extensions.Azure.AzureClientFactoryBuilder UseCredential(System.Func tokenCredentialFactory) { throw null; } } public static partial class AzureClientServiceCollectionExtensions { - public static void AddAzureClientCore(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection) { } public static void AddAzureClients(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection, System.Action configureClients) { } + public static void AddAzureClientsCore(this Microsoft.Extensions.DependencyInjection.IServiceCollection collection) { } } public abstract partial class AzureComponentFactory { protected AzureComponentFactory() { } + public abstract object CreateClient(System.Type clientType, Microsoft.Extensions.Configuration.IConfiguration configuration, Azure.Core.TokenCredential credential, object clientOptions); public abstract object CreateClientOptions(System.Type optionsType, object serviceVersion, Microsoft.Extensions.Configuration.IConfiguration configuration); public abstract Azure.Core.TokenCredential CreateCredential(Microsoft.Extensions.Configuration.IConfiguration configuration); } diff --git a/sdk/extensions/Microsoft.Extensions.Azure/src/AzureClientFactoryBuilder.cs b/sdk/extensions/Microsoft.Extensions.Azure/src/AzureClientFactoryBuilder.cs index 613000fa042db..0f2950083c3df 100644 --- a/sdk/extensions/Microsoft.Extensions.Azure/src/AzureClientFactoryBuilder.cs +++ b/sdk/extensions/Microsoft.Extensions.Azure/src/AzureClientFactoryBuilder.cs @@ -127,18 +127,5 @@ public AzureClientFactoryBuilder UseCredential(Func(options => options.CredentialFactory = tokenCredentialFactory); return this; } - - /// - /// Sets the configuration instance that is used to resolve clients that were not explicitly registered. - /// - /// The delegate that returns a configuration instance that's used to resolve client configuration from. - /// This instance. - public AzureClientFactoryBuilder UseConfiguration(Func configurationProvider) - { - _serviceCollection.Configure(options => options.ConfigurationRootResolver = configurationProvider); - - return this; - } - } } \ No newline at end of file diff --git a/sdk/extensions/Microsoft.Extensions.Azure/src/AzureClientServiceCollectionExtensions.cs b/sdk/extensions/Microsoft.Extensions.Azure/src/AzureClientServiceCollectionExtensions.cs index 8ab6451200574..81f62ac3cc83e 100644 --- a/sdk/extensions/Microsoft.Extensions.Azure/src/AzureClientServiceCollectionExtensions.cs +++ b/sdk/extensions/Microsoft.Extensions.Azure/src/AzureClientServiceCollectionExtensions.cs @@ -22,7 +22,7 @@ public static class AzureClientServiceCollectionExtensions /// An that can be used to configure the client. public static void AddAzureClients(this IServiceCollection collection, Action configureClients) { - collection.AddAzureClientCore(); + collection.AddAzureClientsCore(); configureClients(new AzureClientFactoryBuilder(collection)); } @@ -30,12 +30,11 @@ public static void AddAzureClients(this IServiceCollection collection, Action and to the specified without registering any client types. /// /// The . - public static void AddAzureClientCore(this IServiceCollection collection) + public static void AddAzureClientsCore(this IServiceCollection collection) { collection.AddOptions(); collection.TryAddSingleton(); collection.TryAddSingleton(); - collection.TryAddSingleton(typeof(IAzureClientFactory<>), typeof(FallbackAzureClientFactory<>)); collection.TryAddSingleton(); } } diff --git a/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/AzureClientFactory.cs b/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/AzureClientFactory.cs index bb3aa27904838..c04049e12a6d4 100644 --- a/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/AzureClientFactory.cs +++ b/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/AzureClientFactory.cs @@ -19,8 +19,6 @@ internal class AzureClientFactory: IAzureClientFactory _monitor; private readonly AzureEventSourceLogForwarder _logForwarder; - private readonly AzureComponentFactory _componentFactory; - private FallbackAzureClientFactory _fallbackFactory; public AzureClientFactory( IServiceProvider serviceProvider, @@ -28,8 +26,7 @@ public AzureClientFactory( IOptionsMonitor> clientsOptions, IEnumerable> clientRegistrations, IOptionsMonitor monitor, - AzureEventSourceLogForwarder logForwarder, - AzureComponentFactory componentFactory) + AzureEventSourceLogForwarder logForwarder) { _clientRegistrations = new Dictionary>(); foreach (var registration in clientRegistrations) @@ -42,7 +39,6 @@ public AzureClientFactory( _clientsOptions = clientsOptions; _monitor = monitor; _logForwarder = logForwarder; - _componentFactory = componentFactory; } public TClient CreateClient(string name) @@ -51,12 +47,7 @@ public TClient CreateClient(string name) if (!_clientRegistrations.TryGetValue(name, out ClientRegistration registration)) { - _fallbackFactory ??= new FallbackAzureClientFactory( - _globalOptions, - _serviceProvider, - _componentFactory, - _logForwarder); - return _fallbackFactory.CreateClient(name); + throw new InvalidOperationException($"Unable to find client registration with type '{typeof(TClient).Name}' and name '{name}'."); } return registration.GetClient(_monitor.Get(name), _clientsOptions.Get(name).CredentialFactory(_serviceProvider)); diff --git a/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/AzureComponentFactory.cs b/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/AzureComponentFactory.cs index 52a2d1ff29b33..68621153e41dc 100644 --- a/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/AzureComponentFactory.cs +++ b/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/AzureComponentFactory.cs @@ -25,5 +25,16 @@ public abstract class AzureComponentFactory /// The instance to apply to options. /// A new instance of . public abstract object CreateClientOptions(Type optionsType, object serviceVersion, IConfiguration configuration); + + /// + /// Creates a new client instance using the provided configuration to map constructor parameters from. + /// Optionally takes a set of client option and credential to use when constructing a client. + /// + /// + /// The instance to map constructor parameters from. + /// The object to use if required by constructor, if null no . + /// The client + /// + public abstract object CreateClient(Type clientType, IConfiguration configuration, TokenCredential credential, object clientOptions); } } \ No newline at end of file diff --git a/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/AzureComponentFactoryImpl.cs b/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/AzureComponentFactoryImpl.cs index 93b9518a118f5..61c9bfa7628b5 100644 --- a/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/AzureComponentFactoryImpl.cs +++ b/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/AzureComponentFactoryImpl.cs @@ -27,6 +27,7 @@ public override TokenCredential CreateCredential(IConfiguration configuration) public override object CreateClientOptions(Type optionsType, object serviceVersion, IConfiguration configuration) { + if (optionsType == null) throw new ArgumentNullException(nameof(optionsType)); var options = ClientFactory.CreateClientOptions(serviceVersion, optionsType); if (options is ClientOptions clientOptions) @@ -39,5 +40,14 @@ public override object CreateClientOptions(Type optionsType, object serviceVersi configuration?.Bind(options); return options; } + + public override object CreateClient(Type clientType, IConfiguration configuration, TokenCredential credential, object clientOptions) + { + if (clientType == null) throw new ArgumentNullException(nameof(clientType)); + if (configuration == null) throw new ArgumentNullException(nameof(configuration)); + if (clientOptions == null) throw new ArgumentNullException(nameof(clientOptions)); + + return ClientFactory.CreateClient(clientType, clientOptions.GetType(), clientOptions, configuration, credential); + } } } \ No newline at end of file diff --git a/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/FallbackAzureClientFactory.cs b/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/FallbackAzureClientFactory.cs deleted file mode 100644 index 5f7452adb594e..0000000000000 --- a/sdk/extensions/Microsoft.Extensions.Azure/src/Internal/FallbackAzureClientFactory.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Azure.Core; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Microsoft.Extensions.Azure -{ - internal class FallbackAzureClientFactory: IAzureClientFactory - { - private readonly AzureComponentFactory _componentFactory; - private readonly AzureEventSourceLogForwarder _logForwarder; - private readonly Dictionary> _clientRegistrations; - private readonly Type _clientOptionType; - private readonly IConfiguration _configurationRoot; - private readonly IClientOptionsFactory _optionsFactory; - - public FallbackAzureClientFactory( - IOptionsMonitor globalOptions, - IServiceProvider serviceProvider, - AzureComponentFactory componentFactory, - AzureEventSourceLogForwarder logForwarder) - { - _configurationRoot = globalOptions.CurrentValue.ConfigurationRootResolver?.Invoke(serviceProvider); - - _componentFactory = componentFactory; - _logForwarder = logForwarder; - _clientRegistrations = new Dictionary>(); - - foreach (var constructor in typeof(TClient).GetConstructors(BindingFlags.Public | BindingFlags.Instance)) - { - var lastParameter = constructor.GetParameters().LastOrDefault(); - if (lastParameter != null && typeof(ClientOptions).IsAssignableFrom(lastParameter.ParameterType)) - { - _clientOptionType = lastParameter.ParameterType; - break; - } - } - - if (_clientOptionType == null) - { - throw new InvalidOperationException("Unable to detect the client option type"); - } - - _optionsFactory = (IClientOptionsFactory)ActivatorUtilities.CreateInstance(serviceProvider, typeof(ClientOptionsFactory<,>).MakeGenericType(typeof(TClient), _clientOptionType)); - } - - public TClient CreateClient(string name) - { - if (_configurationRoot == null) - { - throw new InvalidOperationException($"Unable to find client registration with type '{typeof(TClient).Name}' and name '{name}'."); - } - - _logForwarder.Start(); - - FallbackClientRegistration registration; - lock (_clientRegistrations) - { - if (!_clientRegistrations.TryGetValue(name, out registration)) - { - var section = _configurationRoot.GetSection(name); - - if (!section.Exists()) - { - throw new InvalidOperationException($"Unable to find a configuration section with the name {name} to configure the client with."); - } - - registration = new FallbackClientRegistration( - name, - (options, credential) => (TClient) ClientFactory.CreateClient(typeof(TClient), _clientOptionType, options, section, credential), - section); - - _clientRegistrations.Add(name, registration); - } - } - - var currentOptions = _optionsFactory.CreateOptions(name); - registration.Configuration.Bind(currentOptions); - return registration.GetClient(currentOptions, _componentFactory.CreateCredential(registration.Configuration)); - } - - - private class FallbackClientRegistration: ClientRegistration - { - public IConfiguration Configuration { get; } - - public FallbackClientRegistration(string name, Func factory, IConfiguration configuration) : base(name, factory) - { - Configuration = configuration; - } - } - } -} \ No newline at end of file diff --git a/sdk/extensions/Microsoft.Extensions.Azure/tests/AzureClientFactoryTests.cs b/sdk/extensions/Microsoft.Extensions.Azure/tests/AzureClientFactoryTests.cs index 2254c819a67dd..e0e8cea60f63a 100644 --- a/sdk/extensions/Microsoft.Extensions.Azure/tests/AzureClientFactoryTests.cs +++ b/sdk/extensions/Microsoft.Extensions.Azure/tests/AzureClientFactoryTests.cs @@ -309,138 +309,6 @@ public void UsesCredentialFromConfiguration() Assert.AreEqual("ConfigurationTenantId", clientSecretCredential.TenantId); } - [Test] - public void CanCreateClientWithoutRegistration() - { - var configuration = GetConfiguration( - new KeyValuePair("TestClient:uri", "http://localhost/"), - new KeyValuePair("TestClient:clientId", "ConfigurationClientId"), - new KeyValuePair("TestClient:clientSecret", "ConfigurationClientSecret"), - new KeyValuePair("TestClient:tenantId", "ConfigurationTenantId")); - - var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => builder.UseConfiguration(_ => configuration)); - - ServiceProvider provider = serviceCollection.BuildServiceProvider(); - IAzureClientFactory factory = provider.GetService>(); - TestClientWithCredentials client = factory.CreateClient("TestClient"); - - Assert.IsInstanceOf(client.Credential); - var clientSecretCredential = (ClientSecretCredential)client.Credential; - - Assert.AreEqual("http://localhost/", client.Uri.ToString()); - Assert.AreEqual("ConfigurationClientId", clientSecretCredential.ClientId); - Assert.AreEqual("ConfigurationClientSecret", clientSecretCredential.ClientSecret); - Assert.AreEqual("ConfigurationTenantId", clientSecretCredential.TenantId); - } - - [Test] - public void RegistrationOverridesConfigurationBasedClient() - { - var configuration = GetConfiguration( - new KeyValuePair("TestClient:connectionString", "http://localhost/"), - new KeyValuePair("AnotherTestClient:connectionString", "http://betterhost/")); - - var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => { - builder.AddTestClient(configuration.GetSection("AnotherTestClient")).WithName("TestClient"); - builder.UseConfiguration(_ => configuration); - }); - - ServiceProvider provider = serviceCollection.BuildServiceProvider(); - IAzureClientFactory factory = provider.GetService>(); - TestClient client = factory.CreateClient("TestClient"); - - Assert.AreEqual("http://betterhost/", client.ConnectionString); - } - - [Test] - public void RegistrationFallsBackToConfigurationBasedClient() - { - var configuration = GetConfiguration( - new KeyValuePair("TestClient:connectionString", "http://localhost/"), - new KeyValuePair("AnotherTestClient:connectionString", "http://betterhost/")); - - var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => { - builder.AddTestClient(configuration.GetSection("AnotherTestClient")); - builder.UseConfiguration(_ => configuration); - }); - - ServiceProvider provider = serviceCollection.BuildServiceProvider(); - IAzureClientFactory factory = provider.GetService>(); - TestClient client = factory.CreateClient("TestClient"); - TestClient defaultTestClient = provider.GetService(); - - Assert.AreEqual("http://localhost/", client.ConnectionString); - Assert.AreEqual("http://betterhost/", defaultTestClient.ConnectionString); - } - - [Test] - public void CanSetClientOptionsInConfigurationBasedClientsViaConfigureOptions() - { - var configuration = GetConfiguration( - new KeyValuePair("TestClient:connectionString", "http://localhost/"), - new KeyValuePair("TestClient2:connectionString", "http://localhost2/") - ); - - var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => { - builder.UseConfiguration(_ => configuration); - }); - - serviceCollection.ConfigureAll(options => options.Property = "client option value"); - serviceCollection.Configure("TestClient", options => options.IntProperty = 2); - - ServiceProvider provider = serviceCollection.BuildServiceProvider(); - IAzureClientFactory factory = provider.GetService>(); - TestClient client = factory.CreateClient("TestClient"); - TestClient client2 = factory.CreateClient("TestClient2"); - - Assert.AreEqual("http://localhost/", client.ConnectionString); - Assert.AreEqual("client option value", client.Options.Property); - Assert.AreEqual(2, client.Options.IntProperty); - - Assert.AreEqual("http://localhost2/", client2.ConnectionString); - Assert.AreEqual("client option value", client2.Options.Property); - } - - [Test] - public void CanSetClientOptionsInConfigurationBasedClients() - { - var configuration = GetConfiguration( - new KeyValuePair("TestClient:connectionString", "http://localhost/"), - new KeyValuePair("TestClient:Property", "client option value")); - - var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => { - builder.UseConfiguration(_ => configuration); - }); - - ServiceProvider provider = serviceCollection.BuildServiceProvider(); - IAzureClientFactory factory = provider.GetService>(); - TestClient client = factory.CreateClient("TestClient"); - - Assert.AreEqual("http://localhost/", client.ConnectionString); - Assert.AreEqual("client option value", client.Options.Property); - } - - [Test] - public void CanCreateClientWithoutRegistrationUsingConnectionString() - { - var configuration = GetConfiguration( - new KeyValuePair("TestClient", "http://localhost/")); - - var serviceCollection = new ServiceCollection(); - serviceCollection.AddAzureClients(builder => builder.UseConfiguration(_ => configuration)); - - ServiceProvider provider = serviceCollection.BuildServiceProvider(); - IAzureClientFactory factory = provider.GetService>(); - TestClient client = factory.CreateClient("TestClient"); - - Assert.AreEqual("http://localhost/", client.ConnectionString); - } - [Test] public void SupportsSettingVersion() { diff --git a/sdk/extensions/Microsoft.Extensions.Azure/tests/AzureComponentFactoryTests.cs b/sdk/extensions/Microsoft.Extensions.Azure/tests/AzureComponentFactoryTests.cs new file mode 100644 index 0000000000000..f4a221d9475e8 --- /dev/null +++ b/sdk/extensions/Microsoft.Extensions.Azure/tests/AzureComponentFactoryTests.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using Azure.Identity; +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; + +namespace Azure.Core.Extensions.Tests +{ + public class AzureComponentFactoryTests + { + [Test] + public void CanCreateClientWithoutRegistration() + { + var configuration = GetConfiguration( + new KeyValuePair("TestClient:uri", "http://localhost/")); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAzureClientsCore(); + + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + AzureComponentFactory factory = provider.GetService(); + TestClientWithCredentials client = (TestClientWithCredentials) factory.CreateClient(typeof(TestClientWithCredentials), configuration.GetSection("TestClient"), new EnvironmentCredential(), new TestClientOptions()); + + Assert.AreEqual("http://localhost/", client.Uri.ToString()); + Assert.IsInstanceOf(client.Credential); + } + + [Test] + public void CanCreateCredential() + { + var configuration = GetConfiguration( + new KeyValuePair("TestClient:clientId", "ConfigurationClientId"), + new KeyValuePair("TestClient:clientSecret", "ConfigurationClientSecret"), + new KeyValuePair("TestClient:tenantId", "ConfigurationTenantId")); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAzureClientsCore(); + + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + AzureComponentFactory factory = provider.GetService(); + TokenCredential credential = factory.CreateCredential(configuration.GetSection("TestClient")); + + Assert.IsInstanceOf(credential); + var clientSecretCredential = (ClientSecretCredential)credential; + + Assert.AreEqual("ConfigurationClientId", clientSecretCredential.ClientId); + Assert.AreEqual("ConfigurationClientSecret", clientSecretCredential.ClientSecret); + Assert.AreEqual("ConfigurationTenantId", clientSecretCredential.TenantId); + } + + [Test] + public void UsesDefaultCredentialWhenNoneExists() + { + var configuration = GetConfiguration(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAzureClients(builder => builder.UseCredential(new EnvironmentCredential())); + + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + AzureComponentFactory factory = provider.GetService(); + TokenCredential credential = factory.CreateCredential(configuration); + + Assert.IsInstanceOf(credential); + } + + [Test] + public void CanCreateClientWithoutRegistrationUsingConnectionString() + { + var configuration = GetConfiguration( + new KeyValuePair("TestClient", "http://localhost/")); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAzureClientsCore(); + + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + AzureComponentFactory factory = provider.GetService(); + TestClient client = (TestClient) factory.CreateClient(typeof(TestClient), configuration.GetSection("TestClient"), null, new TestClientOptions()); + + Assert.AreEqual("http://localhost/", client.ConnectionString); + } + + [Test] + public void CanReadClientOptionsFromConfiguration() + { + var configuration = GetConfiguration( + new KeyValuePair("TestClient:Property", "client option value")); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAzureClientsCore(); + + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + AzureComponentFactory factory = provider.GetService(); + TestClientOptions options = (TestClientOptions)factory.CreateClientOptions(typeof(TestClientOptions), null, configuration.GetSection("TestClient")); + + Assert.AreEqual("client option value", options.Property); + } + + [Test] + public void UsesProvidedServiceVersionForOptions() + { + var configuration = GetConfiguration( + new KeyValuePair("TestClient:Property", "client option value")); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAzureClientsCore(); + + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + AzureComponentFactory factory = provider.GetService(); + TestClientOptions options = (TestClientOptions)factory.CreateClientOptions(typeof(TestClientOptions), TestClientOptions.ServiceVersion.B, configuration.GetSection("TestClient")); + + Assert.AreEqual("client option value", options.Property); + Assert.AreEqual(TestClientOptions.ServiceVersion.B, options.Version); + } + + [Test] + public void GlobalOptionsAppliedToAzureComponentFactoryCreateClientOptions() + { + var configuration = GetConfiguration(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddAzureClients(builder => builder.ConfigureDefaults(clientOptions => clientOptions.Diagnostics.ApplicationId = "AppId")); + + ServiceProvider provider = serviceCollection.BuildServiceProvider(); + AzureComponentFactory factory = provider.GetService(); + TestClientOptions options = (TestClientOptions)factory.CreateClientOptions(typeof(TestClientOptions), configuration["TestClient"], null); + + Assert.AreEqual("AppId", options.Diagnostics.ApplicationId); + } + + private IConfiguration GetConfiguration(params KeyValuePair[] items) + { + return new ConfigurationBuilder().AddInMemoryCollection(items).Build(); + } + } +} \ No newline at end of file diff --git a/sdk/storage/Azure.Storage.Webjobs.Extensions.Blobs/src/Azure.WebJobs.Extensions.Storage.Blobs.csproj b/sdk/storage/Azure.Storage.Webjobs.Extensions.Blobs/src/Azure.WebJobs.Extensions.Storage.Blobs.csproj index c654ee5270039..8e6f4f12d89a4 100644 --- a/sdk/storage/Azure.Storage.Webjobs.Extensions.Blobs/src/Azure.WebJobs.Extensions.Storage.Blobs.csproj +++ b/sdk/storage/Azure.Storage.Webjobs.Extensions.Blobs/src/Azure.WebJobs.Extensions.Storage.Blobs.csproj @@ -13,8 +13,9 @@ + - + diff --git a/sdk/storage/Azure.Storage.Webjobs.Extensions.Blobs/src/StorageBlobsWebJobsBuilderExtensions.cs b/sdk/storage/Azure.Storage.Webjobs.Extensions.Blobs/src/StorageBlobsWebJobsBuilderExtensions.cs index c5205217ece7c..0591197cf022c 100644 --- a/sdk/storage/Azure.Storage.Webjobs.Extensions.Blobs/src/StorageBlobsWebJobsBuilderExtensions.cs +++ b/sdk/storage/Azure.Storage.Webjobs.Extensions.Blobs/src/StorageBlobsWebJobsBuilderExtensions.cs @@ -14,6 +14,7 @@ using Microsoft.Azure.WebJobs.Host.Blobs.Bindings; using Microsoft.Azure.WebJobs.Host.Queues; using Microsoft.Azure.WebJobs.Host.Queues.Listeners; +using Microsoft.Extensions.Azure; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -32,6 +33,7 @@ public static class StorageBlobsWebJobsBuilderExtensions /// public static IWebJobsBuilder AddAzureStorageBlobs(this IWebJobsBuilder builder, Action configureBlobs = null) { + builder.Services.AddAzureClientsCore(); // $$$ Move to Host.Storage? #pragma warning disable CS0618 // Type or member is obsolete // TODO (kasobol-msft) figure out if this is needed in extension and if so if it's needed in both blobs and queues? diff --git a/sdk/storage/Azure.Storage.Webjobs.Extensions.Blobs/tests/BlobConfigurationTests.cs b/sdk/storage/Azure.Storage.Webjobs.Extensions.Blobs/tests/BlobConfigurationTests.cs index d107e908c93ed..7c764a115c794 100644 --- a/sdk/storage/Azure.Storage.Webjobs.Extensions.Blobs/tests/BlobConfigurationTests.cs +++ b/sdk/storage/Azure.Storage.Webjobs.Extensions.Blobs/tests/BlobConfigurationTests.cs @@ -39,7 +39,7 @@ public async Task BlobClient_CanConnect_ConnectionString() .ConfigureDefaultTestHost(prog, builder => { SetupAzurite(builder); - builder.AddAzureStorageBlobs().AddAzureStorageQueues(); + builder.AddAzureStorageBlobs(); }) .Build(); @@ -73,7 +73,7 @@ public async Task BlobClient_CanConnect_EndPoint() .ConfigureDefaultTestHost(prog, builder => { SetupAzurite(builder); - builder.AddAzureStorageBlobs().AddAzureStorageQueues(); + builder.AddAzureStorageBlobs(); }) .Build(); diff --git a/sdk/storage/Azure.Storage.Webjobs.Extensions.Common/src/Shared/Constants.cs b/sdk/storage/Azure.Storage.Webjobs.Extensions.Common/src/Shared/Constants.cs index 19e664b77c669..5e848c8da7575 100644 --- a/sdk/storage/Azure.Storage.Webjobs.Extensions.Common/src/Shared/Constants.cs +++ b/sdk/storage/Azure.Storage.Webjobs.Extensions.Common/src/Shared/Constants.cs @@ -5,11 +5,6 @@ namespace Microsoft.Azure.WebJobs.Extensions.Storage.Common { internal static class Constants { - public const string WebJobsConfigurationSectionName = "AzureWebJobs"; - public const string EnvironmentSettingName = "AzureWebJobsEnv"; - public const string DevelopmentEnvironmentValue = "Development"; - public const string DynamicSku = "Dynamic"; - public const string AzureWebsiteSku = "WEBSITE_SKU"; public const string DateTimeFormatString = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffK"; } } diff --git a/sdk/storage/Azure.Storage.Webjobs.Extensions.Common/src/Shared/StorageClientProvider.cs b/sdk/storage/Azure.Storage.Webjobs.Extensions.Common/src/Shared/StorageClientProvider.cs index 40fdd9795a3a1..da89e37af365e 100644 --- a/sdk/storage/Azure.Storage.Webjobs.Extensions.Common/src/Shared/StorageClientProvider.cs +++ b/sdk/storage/Azure.Storage.Webjobs.Extensions.Common/src/Shared/StorageClientProvider.cs @@ -8,6 +8,7 @@ using Microsoft.Azure.WebJobs.Host; using Microsoft.Extensions.Azure; using Microsoft.Extensions.Configuration; +using Microsoft.Azure.WebJobs.Extensions.Clients.Shared; namespace Microsoft.Azure.WebJobs.Extensions.Storage.Common { diff --git a/sdk/storage/Azure.Storage.Webjobs.Extensions.Queues/src/Azure.WebJobs.Extensions.Storage.Queues.csproj b/sdk/storage/Azure.Storage.Webjobs.Extensions.Queues/src/Azure.WebJobs.Extensions.Storage.Queues.csproj index bd430366d73ba..611fc611adfee 100644 --- a/sdk/storage/Azure.Storage.Webjobs.Extensions.Queues/src/Azure.WebJobs.Extensions.Storage.Queues.csproj +++ b/sdk/storage/Azure.Storage.Webjobs.Extensions.Queues/src/Azure.WebJobs.Extensions.Storage.Queues.csproj @@ -13,8 +13,9 @@ + - + diff --git a/sdk/storage/Azure.Storage.Webjobs.Extensions.Queues/src/StorageQueuesWebJobsBuilderExtensions.cs b/sdk/storage/Azure.Storage.Webjobs.Extensions.Queues/src/StorageQueuesWebJobsBuilderExtensions.cs index 30287e9b7dfe0..b8c58b8b992f4 100644 --- a/sdk/storage/Azure.Storage.Webjobs.Extensions.Queues/src/StorageQueuesWebJobsBuilderExtensions.cs +++ b/sdk/storage/Azure.Storage.Webjobs.Extensions.Queues/src/StorageQueuesWebJobsBuilderExtensions.cs @@ -33,7 +33,7 @@ public static class StorageQueuesWebJobsBuilderExtensions /// public static IWebJobsBuilder AddAzureStorageQueues(this IWebJobsBuilder builder, Action configureQueues = null) { - builder.Services.AddAzureClients(_ => { }); + builder.Services.AddAzureClientsCore(); builder.Services.TryAddSingleton(); diff --git a/sdk/storage/Directory.Build.props b/sdk/storage/Directory.Build.props index 85fda6bd6d43b..080f51199f92b 100644 --- a/sdk/storage/Directory.Build.props +++ b/sdk/storage/Directory.Build.props @@ -84,5 +84,6 @@ $(MSBuildThisFileDirectory)\Azure.Storage.Common\src\Shared\ $(MSBuildThisFileDirectory)\Azure.Storage.Webjobs.Extensions.Common\src\Shared\ $(MSBuildThisFileDirectory)\Azure.Storage.Webjobs.Extensions.Common\tests\Shared\ + $(MSBuildThisFileDirectory)..\extensions\Microsoft.Azure.WebJobs.Extensions.Clients\src\Shared\ \ No newline at end of file