Skip to content

Commit

Permalink
Add Microsoft.Extensions.Azure.WebJobs package (#14589)
Browse files Browse the repository at this point in the history
  • Loading branch information
pakrym authored Aug 31, 2020
1 parent 4a678fd commit 5fa0036
Show file tree
Hide file tree
Showing 48 changed files with 1,663 additions and 101 deletions.
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

0 comments on commit 5fa0036

Please sign in to comment.