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

Add Microsoft.Extensions.Azure.WebJobs package #14589

Merged
merged 21 commits into from
Aug 31, 2020
Merged
1 change: 1 addition & 0 deletions eng/.docsettings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ known_content_issues:
- ['sdk/extensions/Azure.Extensions.AspNetCore.DataProtection.Blobs/README.md','azure-sdk-tools/issues/404']
- ['sdk/extensions/Azure.Extensions.AspNetCore.DataProtection.Keys/README.md','azure-sdk-tools/issues/404']
- ['sdk/extensions/Azure.Extensions.AspNetCore.Configuration.Secrets/README.md', 'azure-sdk-tools/issues/404']
- ['sdk/extensions/Microsoft.Azure.WebJobs.Extensions.Clients/README.md','azure-sdk-tools/issues/404']
- ['sdk/search/README.md','azure-sdk-tools/issues/42']
- ['sdk/formrecognizer/Azure.AI.FormRecognizer/README.md','#5499']
- ['sdk/formrecognizer/Azure.AI.FormRecognizer/README.md','#11492']
Expand Down
2 changes: 2 additions & 0 deletions eng/Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<PackageReference Update="Microsoft.Azure.Storage.Blob" Version="11.1.7" />
<PackageReference Update="Microsoft.Azure.Storage.Queue" Version="11.1.7" />
<PackageReference Update="Microsoft.Azure.Test.HttpRecorder" Version="[1.13.3, 2.0.0)" />
<PackageReference Update="Microsoft.Azure.WebJobs" Version="3.0.16" />
<PackageReference Update="Microsoft.Bcl.Json.Sources" Version="4.6.0-preview3.19128.7" />
<PackageReference Update="Microsoft.Build.Tasks.Core" Version="15.5.180" />
<PackageReference Update="Microsoft.Build" Version="15.5.180" />
Expand Down Expand Up @@ -142,6 +143,7 @@
<!-- Packages intended for Extensions libraries only -->
<ItemGroup Condition="'$(IsExtensionClientLibrary)' == 'true'">
<PackageReference Update="Microsoft.AspNetCore.DataProtection" Version="2.1.0" />
<PackageReference Update="Microsoft.Extensions.Azure" Version="1.0.0" />
<PackageReference Update="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.0" />
<PackageReference Update="Microsoft.Extensions.Configuration" Version="2.1.0" />
<PackageReference Update="Microsoft.Extensions.Configuration.Abstractions" Version="2.1.0" />
Expand Down
16 changes: 12 additions & 4 deletions sdk/core/Azure.Core.TestFramework/src/RecordedTestAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,23 @@ public override TestResult Execute(TestExecutionContext context)
// Check the result
if (IsTestFailedWithRecordingMismatch(context))
{
var originalResult = context.CurrentResult;
context.CurrentResult = context.CurrentTest.MakeTestResult();
// Run the test again after setting the RecordedTestMode to Record
SetRecordMode(context.TestObject as RecordedTestBase, RecordedTestMode.Record);
context.CurrentResult = innerCommand.Execute(context);

// If the recording succeeded, set a warning result.
if (!IsTestFailedWithRecordingMismatch(context))
// If the recording succeeded, set an error result.
if (context.CurrentResult.ResultState.Status == TestStatus.Passed)
{
context.CurrentResult.SetResult(ResultState.Error, "Test failed playback, but was successfully re-recorded (it should pass if re-run). Please copy updated recording to SessionFiles.");
context.CurrentResult.SetResult(ResultState.Error, "Test failed playback, but was successfully re-recorded (it should pass if re-run). Please copy updated recordings to SessionFiles using `dotnet msbuild /t:UpdateSessionRecords`.");
}
else
{
context.CurrentResult.SetResult(context.CurrentResult.ResultState,
"Error while trying to re-record: " + Environment.NewLine +
context.CurrentResult.Message + Environment.NewLine +
"Original error: " + originalResult.Message, context.CurrentResult.Message);
}

// revert RecordTestMode to Playback
Expand All @@ -71,7 +79,7 @@ private static bool IsTestFailedWithRecordingMismatch(TestExecutionContext conte
_ => true
};

return failed && context.CurrentResult.Message.StartsWith(typeof(TestRecordingMismatchException).FullName);
return failed && context.CurrentResult.Message.Contains(typeof(TestRecordingMismatchException).FullName);
}
}

Expand Down
2 changes: 1 addition & 1 deletion sdk/core/Azure.Core.TestFramework/src/TestRecording.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public TestRecording(RecordedTestMode mode, string sessionFile, RecordedTestSani
{
_session = Load();
}
catch (FileNotFoundException ex)
catch (Exception ex) when (ex is FileNotFoundException || ex is DirectoryNotFoundException)
{
throw new TestRecordingMismatchException(ex.Message, ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ 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; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal AzureClientFactoryBuilder(IServiceCollection serviceCollection)
_serviceCollection = serviceCollection;
_serviceCollection.AddOptions();
_serviceCollection.TryAddSingleton<EventSourceLogForwarder>();
_serviceCollection.TryAddSingleton(typeof(IAzureClientFactory<>), typeof(FallbackAzureClientFactory<>));
}

IAzureClientBuilder<TClient, TOptions> IAzureClientFactoryBuilder.RegisterClientFactory<TClient, TOptions>(Func<TOptions, TClient> clientFactory)
Expand Down Expand Up @@ -91,7 +92,7 @@ public AzureClientFactoryBuilder ConfigureDefaults(IConfiguration configuration)

IAzureClientBuilder<TClient, TOptions> IAzureClientFactoryBuilderWithCredential.RegisterClientFactory<TClient, TOptions>(Func<TOptions, TokenCredential, TClient> clientFactory, bool requiresCredential)
{
var clientRegistration = new ClientRegistration<TClient, TOptions>(DefaultClientName, clientFactory);
var clientRegistration = new ClientRegistration<TClient>(DefaultClientName, (options, credential) => clientFactory((TOptions)options, credential));
clientRegistration.RequiresTokenCredential = requiresCredential;

_serviceCollection.AddSingleton(clientRegistration);
Expand Down Expand Up @@ -128,5 +129,18 @@ 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 @@ -3,6 +3,7 @@

using Microsoft.Extensions.DependencyInjection;
using System;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.Azure
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ public interface IAzureClientFactory<TClient>
/// <returns>An instance of <typeparamref name="TClient"/>.</returns>
TClient CreateClient(string name);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ namespace Microsoft.Extensions.Azure
{
internal sealed class AzureClientBuilder<TClient, TOptions>: IAzureClientBuilder<TClient, TOptions> where TOptions : class
{
public ClientRegistration<TClient, TOptions> Registration { get; }
public ClientRegistration<TClient> Registration { get; }
public IServiceCollection ServiceCollection { get; }

internal AzureClientBuilder(ClientRegistration<TClient, TOptions> clientRegistration, IServiceCollection serviceCollection)
internal AzureClientBuilder(ClientRegistration<TClient> clientRegistration, IServiceCollection serviceCollection)
{
Registration = clientRegistration;
ServiceCollection = serviceCollection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.Extensions.Azure
{
internal class AzureClientFactory<TClient, TOptions>: IAzureClientFactory<TClient>
{
private readonly Dictionary<string, ClientRegistration<TClient, TOptions>> _clientRegistrations;
private readonly Dictionary<string, ClientRegistration<TClient>> _clientRegistrations;

private readonly IServiceProvider _serviceProvider;

Expand All @@ -22,10 +22,11 @@ internal class AzureClientFactory<TClient, TOptions>: IAzureClientFactory<TClien
public AzureClientFactory(
IServiceProvider serviceProvider,
IOptionsMonitor<AzureClientCredentialOptions<TClient>> clientsOptions,
IEnumerable<ClientRegistration<TClient, TOptions>> clientRegistrations, IOptionsMonitor<TOptions> monitor,
IEnumerable<ClientRegistration<TClient>> clientRegistrations,
IOptionsMonitor<TOptions> monitor,
EventSourceLogForwarder logForwarder)
{
_clientRegistrations = new Dictionary<string, ClientRegistration<TClient, TOptions>>();
_clientRegistrations = new Dictionary<string, ClientRegistration<TClient>>();
foreach (var registration in clientRegistrations)
{
_clientRegistrations[registration.Name] = registration;
Expand All @@ -39,7 +40,8 @@ public AzureClientFactory(

public TClient CreateClient(string name)
{
if (!_clientRegistrations.TryGetValue(name, out ClientRegistration<TClient, TOptions> registration))
_logForwarder.Start();
if (!_clientRegistrations.TryGetValue(name, out ClientRegistration<TClient> registration))
{
throw new InvalidOperationException($"Unable to find client registration with type '{typeof(TClient).Name}' and name '{name}'.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
using Azure.Core;
using Azure.Core.Pipeline;
using Azure.Identity;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.Azure
{
internal class AzureClientsGlobalOptions
{
public Func<IServiceProvider, TokenCredential> CredentialFactory { get; set; } = _ => new DefaultAzureCredential();
public List<Action<ClientOptions, IServiceProvider>> ConfigureOptionDelegates { get; } = new List<Action<ClientOptions, IServiceProvider>>();
public Func<IServiceProvider, IConfiguration> ConfigurationRootResolver { get; set; }
}
}
81 changes: 81 additions & 0 deletions sdk/core/Microsoft.Extensions.Azure/src/Internal/ClientFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,23 @@ namespace Microsoft.Extensions.Azure
{
internal static class ClientFactory
{
private const string ServiceVersionParameterTypeName = "ServiceVersion";
private const string ConnectionStringParameterName = "connectionString";

public static object CreateClient(Type clientType, Type optionsType, object options, IConfiguration configuration, TokenCredential credential)
{
List<object> arguments = new List<object>();
// Handle single values as connection strings
if (configuration is IConfigurationSection section && section.Value != null)
{
var connectionString = section.Value;
configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>(ConnectionStringParameterName, connectionString)
})
.Build();
}
foreach (var constructor in clientType.GetConstructors().OrderByDescending(c => c.GetParameters().Length))
{
if (!IsApplicableConstructor(constructor, optionsType))
Expand Down Expand Up @@ -120,6 +134,73 @@ internal static TokenCredential CreateCredential(IConfiguration configuration, T
return null;
}


internal static object CreateClientOptions(object version, Type optionsType)
{
ConstructorInfo parameterlessConstructor = null;
int versionParameterIndex = 0;
object[] constructorArguments = null;

foreach (var constructor in optionsType.GetConstructors())
{
var parameters = constructor.GetParameters();
if (parameters.Length == 0)
{
parameterlessConstructor = constructor;
continue;
}

bool allParametersHaveDefaultValue = true;
for (int i = 0; i < parameters.Length; i++)
{
ParameterInfo parameter = parameters[i];
if (parameter.HasDefaultValue)
{
if (IsServiceVersionParameter(parameter))
{
versionParameterIndex = i;
}
}
else
{
allParametersHaveDefaultValue = false;
break;
}
}

if (allParametersHaveDefaultValue)
{
constructorArguments = new object[parameters.Length];

for (int i = 0; i < parameters.Length; i++)
{
constructorArguments[i] = parameters[i].DefaultValue;
}
}
}

if (version != null)
{
if (constructorArguments != null)
{
constructorArguments[versionParameterIndex] = version;
return Activator.CreateInstance(optionsType, constructorArguments);
}

throw new InvalidOperationException("Unable to find constructor that takes service version");
}

if (parameterlessConstructor != null)
{
return Activator.CreateInstance(optionsType);
}

return Activator.CreateInstance(optionsType, constructorArguments);
}

private static bool IsServiceVersionParameter(ParameterInfo parameter) =>
parameter.ParameterType.Name == ServiceVersionParameterTypeName;

private static bool IsCredentialParameter(ParameterInfo parameter)
{
return parameter.ParameterType == typeof(TokenCredential);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@ namespace Microsoft.Extensions.Azure
// Slightly adjusted copy of https://github.com/aspnet/Extensions/blob/master/src/Options/Options/src/OptionsFactory.cs
internal class ClientOptionsFactory<TClient, TOptions> where TOptions : class
{
private const string ServiceVersionParameterTypeName = "ServiceVersion";

private readonly IEnumerable<IConfigureOptions<TOptions>> _setups;
private readonly IEnumerable<IPostConfigureOptions<TOptions>> _postConfigures;

private readonly IEnumerable<ClientRegistration<TClient, TOptions>> _clientRegistrations;
private readonly IEnumerable<ClientRegistration<TClient>> _clientRegistrations;

public ClientOptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, IEnumerable<ClientRegistration<TClient, TOptions>> clientRegistrations)
public ClientOptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, IEnumerable<ClientRegistration<TClient>> clientRegistrations)
{
_setups = setups;
_postConfigures = postConfigures;
Expand All @@ -37,70 +35,9 @@ private TOptions CreateOptions(string name)
}
}

ConstructorInfo parameterlessConstructor = null;
int versionParameterIndex = 0;
object[] constructorArguments = null;

foreach (var constructor in typeof(TOptions).GetConstructors())
{
var parameters = constructor.GetParameters();
if (parameters.Length == 0)
{
parameterlessConstructor = constructor;
continue;
}

bool allParametersHaveDefaultValue = true;
for (int i = 0; i < parameters.Length; i++)
{
ParameterInfo parameter = parameters[i];
if (parameter.HasDefaultValue)
{
if (IsServiceVersionParameter(parameter))
{
versionParameterIndex = i;
}
}
else
{
allParametersHaveDefaultValue = false;
break;
}
}

if (allParametersHaveDefaultValue)
{
constructorArguments = new object[parameters.Length];

for (int i = 0; i < parameters.Length; i++)
{
constructorArguments[i] = parameters[i].DefaultValue;
}
}
}

if (version != null)
{
if (constructorArguments != null)
{
constructorArguments[versionParameterIndex] = version;
return (TOptions)Activator.CreateInstance(typeof(TOptions), constructorArguments);
}

throw new InvalidOperationException("Unable to find constructor that takes service version");
}

if (parameterlessConstructor != null)
{
return Activator.CreateInstance<TOptions>();
}

return (TOptions)Activator.CreateInstance(typeof(TOptions), constructorArguments);
return (TOptions)ClientFactory.CreateClientOptions(version, typeof(TOptions));
}

private static bool IsServiceVersionParameter(ParameterInfo parameter) =>
parameter.ParameterType.Name == ServiceVersionParameterTypeName;

/// <summary>
/// Returns a configured <typeparamref name="TOptions"/> instance with the given <paramref name="name"/>.
/// </summary>
Expand Down
Loading