Skip to content

Commit

Permalink
Move configuration-based client logic to Microsoft.Azure.WebJobs.Exte…
Browse files Browse the repository at this point in the history
…nsions.Clients (Azure#15804)
  • Loading branch information
pakrym authored and suhas92 committed Oct 12, 2020
1 parent 666b224 commit c7d69b3
Show file tree
Hide file tree
Showing 21 changed files with 240 additions and 301 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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<TClient> : 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<object> GetValueAsync()
{
return Task.FromResult(
(object)_serviceProvider
.GetRequiredService<IAzureClientFactory<TClient>>()
.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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<IConfiguration>())));
builder.Services.AddAzureClientsCore();
builder.AddExtension<AzureClientsExtensionConfigProvider>();

return builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,7 +26,7 @@ public static IConfigurationSection GetWebJobsConnectionStringSection(this IConf

public static string GetPrefixedConnectionStringName(string connectionStringName)
{
return Constants.WebJobsConfigurationSectionName + connectionStringName;
return WebJobsConfigurationSectionName + connectionStringName;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,9 +21,8 @@ public void KeysAreMappedForSingleValue(string actualKeyName)
{
new KeyValuePair<string, string>(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")]
Expand All @@ -36,10 +36,10 @@ public void KeysAreMappedForSections(string actualKeyName)
new KeyValuePair<string, string>(actualKeyName + ":Value1", "value1"),
new KeyValuePair<string, string>(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");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Azure.Core.ClientOptions, System.IServiceProvider> configureOptions) { throw null; }
public Microsoft.Extensions.Azure.AzureClientFactoryBuilder ConfigureDefaults(System.Action<Azure.Core.ClientOptions> configureOptions) { throw null; }
public Microsoft.Extensions.Azure.AzureClientFactoryBuilder UseConfiguration(System.Func<System.IServiceProvider, Microsoft.Extensions.Configuration.IConfiguration> configurationProvider) { throw null; }
public Microsoft.Extensions.Azure.AzureClientFactoryBuilder UseCredential(Azure.Core.TokenCredential tokenCredential) { throw null; }
public Microsoft.Extensions.Azure.AzureClientFactoryBuilder UseCredential(System.Func<System.IServiceProvider, Azure.Core.TokenCredential> 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<Microsoft.Extensions.Azure.AzureClientFactoryBuilder> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,5 @@ public AzureClientFactoryBuilder UseCredential(Func<IServiceProvider, TokenCrede
_serviceCollection.Configure<AzureClientsGlobalOptions>(options => options.CredentialFactory = tokenCredentialFactory);
return this;
}

/// <summary>
/// Sets the configuration instance that is used to resolve clients that were not explicitly registered.
/// </summary>
/// <param name="configurationProvider">The delegate that returns a configuration instance that's used to resolve client configuration from.</param>
/// <returns>This instance.</returns>
public AzureClientFactoryBuilder UseConfiguration(Func<IServiceProvider, IConfiguration> configurationProvider)
{
_serviceCollection.Configure<AzureClientsGlobalOptions>(options => options.ConfigurationRootResolver = configurationProvider);

return this;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,19 @@ public static class AzureClientServiceCollectionExtensions
/// <param name="configureClients">An <see cref="AzureClientFactoryBuilder"/> that can be used to configure the client.</param>
public static void AddAzureClients(this IServiceCollection collection, Action<AzureClientFactoryBuilder> configureClients)
{
collection.AddAzureClientCore();
collection.AddAzureClientsCore();
configureClients(new AzureClientFactoryBuilder(collection));
}

/// <summary>
/// Adds the minimum essential Azure SDK interop services like <see cref="AzureEventSourceLogForwarder"/> and <see cref="AzureComponentFactory"/> to the specified <see cref="IServiceCollection"/> without registering any client types.
/// </summary>
/// <param name="collection">The <see cref="IServiceCollection"/>.</param>
public static void AddAzureClientCore(this IServiceCollection collection)
public static void AddAzureClientsCore(this IServiceCollection collection)
{
collection.AddOptions();
collection.TryAddSingleton<AzureEventSourceLogForwarder>();
collection.TryAddSingleton<ILoggerFactory, NullLoggerFactory>();
collection.TryAddSingleton(typeof(IAzureClientFactory<>), typeof(FallbackAzureClientFactory<>));
collection.TryAddSingleton<AzureComponentFactory, AzureComponentFactoryImpl>();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,14 @@ internal class AzureClientFactory<TClient, TOptions>: IAzureClientFactory<TClien
private readonly IOptionsMonitor<TOptions> _monitor;

private readonly AzureEventSourceLogForwarder _logForwarder;
private readonly AzureComponentFactory _componentFactory;
private FallbackAzureClientFactory<TClient> _fallbackFactory;

public AzureClientFactory(
IServiceProvider serviceProvider,
IOptionsMonitor<AzureClientsGlobalOptions> globalOptions,
IOptionsMonitor<AzureClientCredentialOptions<TClient>> clientsOptions,
IEnumerable<ClientRegistration<TClient>> clientRegistrations,
IOptionsMonitor<TOptions> monitor,
AzureEventSourceLogForwarder logForwarder,
AzureComponentFactory componentFactory)
AzureEventSourceLogForwarder logForwarder)
{
_clientRegistrations = new Dictionary<string, ClientRegistration<TClient>>();
foreach (var registration in clientRegistrations)
Expand All @@ -42,7 +39,6 @@ public AzureClientFactory(
_clientsOptions = clientsOptions;
_monitor = monitor;
_logForwarder = logForwarder;
_componentFactory = componentFactory;
}

public TClient CreateClient(string name)
Expand All @@ -51,12 +47,7 @@ public TClient CreateClient(string name)

if (!_clientRegistrations.TryGetValue(name, out ClientRegistration<TClient> registration))
{
_fallbackFactory ??= new FallbackAzureClientFactory<TClient>(
_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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,16 @@ public abstract class AzureComponentFactory
/// <param name="configuration">The <see cref="IConfiguration"/> instance to apply to options.</param>
/// <returns>A new instance of <paramref name="optionsType"/>.</returns>
public abstract object CreateClientOptions(Type optionsType, object serviceVersion, IConfiguration configuration);

/// <summary>
/// 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.
/// </summary>
/// <param name="clientType"></param>
/// <param name="configuration">The <see cref="IConfiguration"/> instance to map constructor parameters from.</param>
/// <param name="credential">The <see cref="TokenCredential"/> object to use if required by constructor, if null no .</param>
/// <param name="clientOptions">The client </param>
/// <returns></returns>
public abstract object CreateClient(Type clientType, IConfiguration configuration, TokenCredential credential, object clientOptions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
}
}
}
Loading

0 comments on commit c7d69b3

Please sign in to comment.