From 8db04f1c9d99bd1700ad7f2c9b41ffa40145945d Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Tue, 30 Jul 2019 16:51:32 -0700 Subject: [PATCH 1/3] Use AsyncCollection in shared helper --- ...zure.ApplicationModel.Configuration.csproj | 1 - .../src/ConfigurationClient.cs | 20 +++---- sdk/core/Azure.Core/src/AsyncCollection.cs | 11 +++- .../Azure.Core/src/Shared/PageResponse.cs | 21 ------- .../src/Shared/PageResponseEnumerator.cs | 39 ++++++++----- .../UseSyncMethodsInterceptor.cs | 55 ++++++++++--------- .../samples/Startup.cs | 4 +- .../src/Azure.Security.KeyVault.Keys.csproj | 1 - .../src/KeyClient.cs | 6 +- .../src/KeyClient_private.cs | 12 ++-- .../Azure.Security.KeyVault.Keys/src/Page.cs | 6 +- .../Azure.Security.KeyVault.Secrets.csproj | 1 - .../src/Page.cs | 4 +- .../src/SecretClient.cs | 18 +++--- 14 files changed, 101 insertions(+), 98 deletions(-) delete mode 100644 sdk/core/Azure.Core/src/Shared/PageResponse.cs diff --git a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/Azure.ApplicationModel.Configuration.csproj b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/Azure.ApplicationModel.Configuration.csproj index 92019df4b546f..66f59ab661c4a 100644 --- a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/Azure.ApplicationModel.Configuration.csproj +++ b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/Azure.ApplicationModel.Configuration.csproj @@ -26,7 +26,6 @@ - diff --git a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/ConfigurationClient.cs b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/ConfigurationClient.cs index 52585b4376901..1c1a28a1f1ec0 100644 --- a/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/ConfigurationClient.cs +++ b/sdk/appconfiguration/Azure.ApplicationModel.Configuration/src/ConfigurationClient.cs @@ -569,7 +569,7 @@ public virtual Response Get(string key, string label = def /// /// Set of options for selecting from the configuration store. /// A controlling the request lifetime. - public virtual IAsyncEnumerable> GetSettingsAsync(SettingSelector selector, CancellationToken cancellationToken = default) + public virtual AsyncCollection GetSettingsAsync(SettingSelector selector, CancellationToken cancellationToken = default) { return PageResponseEnumerator.CreateAsyncEnumerable(nextLink => GetSettingsPageAsync(selector, nextLink, cancellationToken)); } @@ -589,7 +589,7 @@ public virtual IEnumerable> GetSettings(SettingSe /// /// Set of options for selecting from the configuration store. /// A controlling the request lifetime. - public virtual IAsyncEnumerable> GetRevisionsAsync(SettingSelector selector, CancellationToken cancellationToken = default) + public virtual AsyncCollection GetRevisionsAsync(SettingSelector selector, CancellationToken cancellationToken = default) { return PageResponseEnumerator.CreateAsyncEnumerable(nextLink => GetRevisionsPageAsync(selector, nextLink, cancellationToken)); } @@ -630,7 +630,7 @@ private Request CreateGetRequest(string key, string label, DateTimeOffset accept /// Set of options for selecting settings from the configuration store. /// /// A controlling the request lifetime. - private async Task> GetSettingsPageAsync(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default) + private async Task> GetSettingsPageAsync(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default) { using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.GetSettingsPage"); scope.Start(); @@ -645,7 +645,7 @@ private async Task> GetSettingsPageAsync(Sett case 200: case 206: SettingBatch settingBatch = await ConfigurationServiceSerializer.ParseBatchAsync(response, cancellationToken).ConfigureAwait(false); - return new PageResponse(settingBatch.Settings, response, settingBatch.NextBatchLink); + return new Page(settingBatch.Settings, settingBatch.NextBatchLink, response); default: throw await response.CreateRequestFailedExceptionAsync().ConfigureAwait(false); } @@ -663,7 +663,7 @@ private async Task> GetSettingsPageAsync(Sett /// Set of options for selecting settings from the configuration store. /// /// A controlling the request lifetime. - private PageResponse GetSettingsPage(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default) + private Page GetSettingsPage(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default) { using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.GetSettingsPage"); scope.Start(); @@ -678,7 +678,7 @@ private PageResponse GetSettingsPage(SettingSelector selec case 200: case 206: SettingBatch settingBatch = ConfigurationServiceSerializer.ParseBatch(response); - return new PageResponse(settingBatch.Settings, response, settingBatch.NextBatchLink); + return new Page(settingBatch.Settings, settingBatch.NextBatchLink, response); default: throw response.CreateRequestFailedException(); } @@ -712,7 +712,7 @@ private Request CreateBatchRequest(SettingSelector selector, string pageLink) /// Set of options for selecting settings from the configuration store. /// /// A controlling the request lifetime. - private async Task> GetRevisionsPageAsync(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default) + private async Task> GetRevisionsPageAsync(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default) { using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.GetRevisionsPage"); scope.Start(); @@ -726,7 +726,7 @@ private async Task> GetRevisionsPageAsync(Set case 200: case 206: SettingBatch settingBatch = await ConfigurationServiceSerializer.ParseBatchAsync(response, cancellationToken).ConfigureAwait(false); - return new PageResponse(settingBatch.Settings, response, settingBatch.NextBatchLink); + return new Page(settingBatch.Settings, settingBatch.NextBatchLink, response); default: throw await response.CreateRequestFailedExceptionAsync().ConfigureAwait(false); } @@ -745,7 +745,7 @@ private async Task> GetRevisionsPageAsync(Set /// Set of options for selecting settings from the configuration store. /// /// A controlling the request lifetime. - private PageResponse GetRevisionsPage(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default) + private Page GetRevisionsPage(SettingSelector selector, string pageLink, CancellationToken cancellationToken = default) { using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope("Azure.ApplicationModel.Configuration.ConfigurationClient.GetRevisionsPage"); scope.Start(); @@ -759,7 +759,7 @@ private PageResponse GetRevisionsPage(SettingSelector sele case 200: case 206: SettingBatch settingBatch = ConfigurationServiceSerializer.ParseBatch(response); - return new PageResponse(settingBatch.Settings, response, settingBatch.NextBatchLink); + return new Page(settingBatch.Settings, settingBatch.NextBatchLink, response); default: throw response.CreateRequestFailedException(); } diff --git a/sdk/core/Azure.Core/src/AsyncCollection.cs b/sdk/core/Azure.Core/src/AsyncCollection.cs index 717c81039c1b5..783f9b3de4451 100644 --- a/sdk/core/Azure.Core/src/AsyncCollection.cs +++ b/sdk/core/Azure.Core/src/AsyncCollection.cs @@ -66,7 +66,16 @@ public abstract IAsyncEnumerable> ByPage( /// enumerating asynchronously. /// /// An async sequence of values. - public abstract IAsyncEnumerator> GetAsyncEnumerator(CancellationToken cancellationToken = default); + public virtual async IAsyncEnumerator> GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + await foreach (Page page in ByPage()) + { + foreach (T value in page.Values) + { + yield return new Response(page.GetRawResponse(), value); + } + } + } /// /// Creates a string representation of an . diff --git a/sdk/core/Azure.Core/src/Shared/PageResponse.cs b/sdk/core/Azure.Core/src/Shared/PageResponse.cs deleted file mode 100644 index 5e861685868cb..0000000000000 --- a/sdk/core/Azure.Core/src/Shared/PageResponse.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Collections.Generic; - -namespace Azure.Core -{ - internal struct PageResponse - { - public IEnumerable Values { get; } - public Response Response { get; } - public string NextLink { get; } - - public PageResponse(IEnumerable values, Response response, string nextLink) - { - Values = values; - Response = response; - NextLink = nextLink; - } - } -} diff --git a/sdk/core/Azure.Core/src/Shared/PageResponseEnumerator.cs b/sdk/core/Azure.Core/src/Shared/PageResponseEnumerator.cs index c7438a3f9ce57..6d01f6657c811 100644 --- a/sdk/core/Azure.Core/src/Shared/PageResponseEnumerator.cs +++ b/sdk/core/Azure.Core/src/Shared/PageResponseEnumerator.cs @@ -2,39 +2,52 @@ // Licensed under the MIT License. using System; +using System.Collections; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Azure.Core { internal static class PageResponseEnumerator { - public static IEnumerable> CreateEnumerable(Func> pageFunc) + public static IEnumerable> CreateEnumerable(Func> pageFunc) { string nextLink = null; do { - PageResponse pageResponse = pageFunc(nextLink); + Page pageResponse = pageFunc(nextLink); foreach (T setting in pageResponse.Values) { - yield return new Response(pageResponse.Response, setting); + yield return new Response(pageResponse.GetRawResponse(), setting); } - nextLink = pageResponse.NextLink; + nextLink = pageResponse.ContinuationToken; } while (nextLink != null); } - public static async IAsyncEnumerable> CreateAsyncEnumerable(Func>> pageFunc) + public static AsyncCollection CreateAsyncEnumerable(Func>> pageFunc) { - string nextLink = null; - do + return new FuncAsyncCollection(pageFunc); + } + + internal class FuncAsyncCollection: AsyncCollection + { + private readonly Func>> _pageFunc; + + public FuncAsyncCollection(Func>> pageFunc) { - PageResponse pageResponse = await pageFunc(nextLink).ConfigureAwait(false); - foreach (T setting in pageResponse.Values) + _pageFunc = pageFunc; + } + + public override async IAsyncEnumerable> ByPage(string continuationToken = default, int? pageSizeHint = default) + { + do { - yield return new Response(pageResponse.Response, setting); - } - nextLink = pageResponse.NextLink; - } while (nextLink != null); + Page pageResponse = await _pageFunc(continuationToken).ConfigureAwait(false); + yield return pageResponse; + continuationToken = pageResponse.ContinuationToken; + } while (continuationToken != null); + } } } } diff --git a/sdk/core/Azure.Core/tests/TestFramework/UseSyncMethodsInterceptor.cs b/sdk/core/Azure.Core/tests/TestFramework/UseSyncMethodsInterceptor.cs index 476105113165c..680096694f4c6 100644 --- a/sdk/core/Azure.Core/tests/TestFramework/UseSyncMethodsInterceptor.cs +++ b/sdk/core/Azure.Core/tests/TestFramework/UseSyncMethodsInterceptor.cs @@ -82,20 +82,10 @@ public void Intercept(IInvocation invocation) // Map IEnumerable to IAsyncEnumerable if (returnsIEnumerable) { - if (invocation.Method.ReturnType.IsGenericType && - invocation.Method.ReturnType.GetGenericTypeDefinition().Name == "AsyncCollection`1") - { - // AsyncCollection can be used as either a sync or async - // collection so there's no need to wrap it in an - // IAsyncEnumerable - invocation.ReturnValue = result; - } - else - { - invocation.ReturnValue = Activator.CreateInstance( - typeof(AsyncEnumerableWrapper<>).MakeGenericType(returnType.GenericTypeArguments), - new[] { result }); - } + Type[] modelType = returnType.GenericTypeArguments.Single().GenericTypeArguments; + Type wrapperType = typeof(AsyncEnumerableWrapper<>).MakeGenericType(modelType); + + invocation.ReturnValue = Activator.CreateInstance(wrapperType, new [] { result }); } else { @@ -120,34 +110,47 @@ private static MethodInfo GetMethod(IInvocation invocation, string nonAsyncMetho return invocation.TargetType.GetMethod(nonAsyncMethodName, BindingFlags.Public | BindingFlags.Instance, null, types, null); } - private class AsyncEnumerableWrapper : IAsyncEnumerable + private class AsyncEnumerableWrapper : AsyncCollection { - private readonly IEnumerable _enumerable; + private readonly IEnumerable> _enumerable; - public AsyncEnumerableWrapper(IEnumerable enumerable) + public AsyncEnumerableWrapper(IEnumerable> enumerable) { _enumerable = enumerable; } - public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()) +#pragma warning disable 1998 + public override async IAsyncEnumerable> ByPage(string continuationToken = default, int? pageSizeHint = default) +#pragma warning restore 1998 { - return new Enumerator(_enumerable.GetEnumerator()); + if (continuationToken != null) + { + throw new InvalidOperationException("Calling ByPage with a continuationToken is not supported in the sync mode"); + } + + foreach (Response response in _enumerable) + { + yield return new Page(new [] { response.Value}, null, response.GetRawResponse()); + } } - private class Enumerator: IAsyncEnumerator + private class SingleEnumerable: IAsyncEnumerable>, IAsyncEnumerator> { - private readonly IEnumerator _enumerator; - - public Enumerator(IEnumerator enumerator) + public SingleEnumerable(Page value) { - _enumerator = enumerator; + Current = value; } public ValueTask DisposeAsync() => default; - public ValueTask MoveNextAsync() => new ValueTask(_enumerator.MoveNext()); + public ValueTask MoveNextAsync() => new ValueTask(false); + + public Page Current { get; } - public T Current => _enumerator.Current; + public IAsyncEnumerator> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()) + { + return this; + } } } } diff --git a/sdk/core/Microsoft.Extensions.Azure/samples/Startup.cs b/sdk/core/Microsoft.Extensions.Azure/samples/Startup.cs index 683a82aebd25c..047b816886d24 100644 --- a/sdk/core/Microsoft.Extensions.Azure/samples/Startup.cs +++ b/sdk/core/Microsoft.Extensions.Azure/samples/Startup.cs @@ -25,7 +25,7 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) + public IServiceProvider ConfigureServices(IServiceCollection services) { // Registering policy to use in ConfigureDefaults later services.AddSingleton(); @@ -53,6 +53,8 @@ public void ConfigureServices(IServiceCollection services) builder.AddBlobServiceClient(Configuration.GetSection("Storage")) .WithVersion(BlobClientOptions.ServiceVersion.V2018_11_09); }); + + return services.BuildServiceProvider(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Azure.Security.KeyVault.Keys.csproj b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Azure.Security.KeyVault.Keys.csproj index 24715c8013364..778df79ad1948 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Azure.Security.KeyVault.Keys.csproj +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Azure.Security.KeyVault.Keys.csproj @@ -26,7 +26,6 @@ - diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient.cs b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient.cs index 680dec71f0c5c..1f92a29db61ba 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient.cs @@ -404,7 +404,7 @@ public virtual IEnumerable> GetKeys(CancellationToken cancella /// permission. /// /// A controlling the request lifetime. - public virtual IAsyncEnumerable> GetKeysAsync(CancellationToken cancellationToken = default) + public virtual AsyncCollection GetKeysAsync(CancellationToken cancellationToken = default) { Uri firstPageUri = CreateFirstPageUri(KeysPath); @@ -438,7 +438,7 @@ public virtual IEnumerable> GetKeyVersions(string name, Cancel /// /// The name of the key. /// A controlling the request lifetime. - public virtual IAsyncEnumerable> GetKeyVersionsAsync(string name, CancellationToken cancellationToken = default) + public virtual AsyncCollection GetKeyVersionsAsync(string name, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} can't be empty or null"); @@ -600,7 +600,7 @@ public virtual IEnumerable> GetDeletedKeys(CancellationToke /// vault. This operation requires the keys/list permission. /// /// A controlling the request lifetime. - public virtual IAsyncEnumerable> GetDeletedKeysAsync(CancellationToken cancellationToken = default) + public virtual AsyncCollection GetDeletedKeysAsync(CancellationToken cancellationToken = default) { Uri firstPageUri = CreateFirstPageUri(DeletedKeysPath); diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient_private.cs b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient_private.cs index 95d5ed080a15a..7ef5b3b3c9357 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient_private.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyClient_private.cs @@ -161,7 +161,7 @@ private Response CreateResponse(Response response, T result) return new Response(response, result); } - private async Task> GetPageAsync(Uri firstPageUri, string nextLink, Func itemFactory, string operationName, CancellationToken cancellationToken) + private async Task> GetPageAsync(Uri firstPageUri, string nextLink, Func itemFactory, string operationName, CancellationToken cancellationToken) where T : Model { using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope(operationName); @@ -180,11 +180,11 @@ private async Task> GetPageAsync(Uri firstPageUri, string nex Response response = await SendRequestAsync(request, cancellationToken).ConfigureAwait(false); // read the respose - Page responseAsPage = new Page(itemFactory); + KeyVaultPage responseAsPage = new KeyVaultPage(itemFactory); responseAsPage.Deserialize(response.ContentStream); // convert from the Page to PageResponse - return new PageResponse(responseAsPage.Items.ToArray(), response, responseAsPage.NextLink?.ToString()); + return new Page(responseAsPage.Items.ToArray(), responseAsPage.NextLink?.ToString(), response); } } catch (Exception e) @@ -194,7 +194,7 @@ private async Task> GetPageAsync(Uri firstPageUri, string nex } } - private PageResponse GetPage(Uri firstPageUri, string nextLink, Func itemFactory, string operationName, CancellationToken cancellationToken) + private Page GetPage(Uri firstPageUri, string nextLink, Func itemFactory, string operationName, CancellationToken cancellationToken) where T : Model { using DiagnosticScope scope = _pipeline.Diagnostics.CreateScope(operationName); @@ -213,11 +213,11 @@ private PageResponse GetPage(Uri firstPageUri, string nextLink, Func it Response response = SendRequest(request, cancellationToken); // read the respose - Page responseAsPage = new Page(itemFactory); + KeyVaultPage responseAsPage = new KeyVaultPage(itemFactory); responseAsPage.Deserialize(response.ContentStream); // convert from the Page to PageResponse - return new PageResponse(responseAsPage.Items.ToArray(), response, responseAsPage.NextLink?.ToString()); + return new Page(responseAsPage.Items.ToArray(), responseAsPage.NextLink?.ToString(), response); } } catch (Exception e) diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Page.cs b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Page.cs index 2d0c48984ea94..f7f6b4cceabb4 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Page.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/src/Page.cs @@ -11,14 +11,14 @@ namespace Azure.Security.KeyVault.Keys /// Defines a page in Azure responses. /// /// Type of the page content items - public class Page : Model + internal class KeyVaultPage : Model where T : Model { private T[] _items; private Uri _nextLink; private Func _itemFactory; - internal Page(Func itemFactory) + internal KeyVaultPage(Func itemFactory) { _itemFactory = itemFactory; } @@ -64,7 +64,7 @@ internal override void ReadProperties(JsonElement json) internal override void WriteProperties(Utf8JsonWriter json) { - // serialization is not needed this type is only in responses + // serialization is not needed this type is only in responses throw new NotImplementedException(); } } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj index bdab447e61224..99851117fa17a 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Azure.Security.KeyVault.Secrets.csproj @@ -24,7 +24,6 @@ - diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Page.cs b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Page.cs index c1b137b32fe62..af4a24dcc327c 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Page.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/Page.cs @@ -11,14 +11,14 @@ namespace Azure.Security.KeyVault.Secrets /// Defines a page in Azure responses. /// /// Type of the page content items - public class Page : Model + internal class KeyVaultPage : Model where T : Model { private T[] _items; private Uri _nextLink; private Func _itemFactory; - internal Page(Func itemFactory) + internal KeyVaultPage(Func itemFactory) { _itemFactory = itemFactory; } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretClient.cs b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretClient.cs index de8e45e30309f..d724983561354 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretClient.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/src/SecretClient.cs @@ -130,7 +130,7 @@ public virtual Response Get(string name, string version = null, Cancella /// /// The name of the secret. /// A controlling the request lifetime. - public virtual IAsyncEnumerable> GetSecretVersionsAsync(string name, CancellationToken cancellationToken = default) + public virtual AsyncCollection GetSecretVersionsAsync(string name, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(name)) throw new ArgumentException($"{nameof(name)} must not be null or empty", nameof(name)); @@ -168,7 +168,7 @@ public virtual IEnumerable> GetSecretVersions(string name, /// requires the secrets/list permission. /// /// A controlling the request lifetime. - public virtual IAsyncEnumerable> GetSecretsAsync(CancellationToken cancellationToken = default) + public virtual AsyncCollection GetSecretsAsync(CancellationToken cancellationToken = default) { Uri firstPageUri = new Uri(_vaultUri, SecretsPath + $"?api-version={ApiVersion}"); @@ -465,7 +465,7 @@ public virtual Response GetDeleted(string name, CancellationToken /// secrets/list permission. /// /// A controlling the request lifetime. - public virtual IAsyncEnumerable> GetDeletedSecretsAsync(CancellationToken cancellationToken = default) + public virtual AsyncCollection GetDeletedSecretsAsync(CancellationToken cancellationToken = default) { Uri firstPageUri = new Uri(_vaultUri, DeletedSecretsPath + $"?api-version={ApiVersion}"); @@ -809,7 +809,7 @@ private Response SendRequest(Request request, CancellationToken cancellationToke } } - private async Task> GetPageAsync(Uri firstPageUri, string nextLink, Func itemFactory, string operationName, CancellationToken cancellationToken) + private async Task> GetPageAsync(Uri firstPageUri, string nextLink, Func itemFactory, string operationName, CancellationToken cancellationToken) where T : Model { // if we don't have a nextLink specified, use firstPageUri @@ -828,11 +828,11 @@ private async Task> GetPageAsync(Uri firstPageUri, string nex Response response = await SendRequestAsync(request, cancellationToken).ConfigureAwait(false); // read the respose - Page responseAsPage = new Page(itemFactory); + KeyVaultPage responseAsPage = new KeyVaultPage(itemFactory); responseAsPage.Deserialize(response.ContentStream); // convert from the Page to PageResponse - return new PageResponse(responseAsPage.Items.ToArray(), response, responseAsPage.NextLink?.ToString()); + return new Page(responseAsPage.Items.ToArray(), responseAsPage.NextLink?.ToString(), response); } } catch (Exception e) @@ -842,7 +842,7 @@ private async Task> GetPageAsync(Uri firstPageUri, string nex } } - private PageResponse GetPage(Uri firstPageUri, string nextLink, Func itemFactory, string operationName, CancellationToken cancellationToken) + private Page GetPage(Uri firstPageUri, string nextLink, Func itemFactory, string operationName, CancellationToken cancellationToken) where T : Model { // if we don't have a nextLink specified, use firstPageUri @@ -861,11 +861,11 @@ private PageResponse GetPage(Uri firstPageUri, string nextLink, Func it Response response = SendRequest(request, cancellationToken); // read the respose - Page responseAsPage = new Page(itemFactory); + KeyVaultPage responseAsPage = new KeyVaultPage(itemFactory); responseAsPage.Deserialize(response.ContentStream); // convert from the Page to PageResponse - return new PageResponse(responseAsPage.Items.ToArray(), response, responseAsPage.NextLink?.ToString()); + return new Page(responseAsPage.Items.ToArray(), responseAsPage.NextLink?.ToString(), response); } } catch (Exception e) From db79addaa7079eacec06529fd15c70b68071ae08 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 31 Jul 2019 09:14:19 -0700 Subject: [PATCH 2/3] FB --- sdk/core/Azure.Core/src/AsyncCollection.cs | 3 ++- .../Azure.Core/src/Shared/PageResponseEnumerator.cs | 11 ++++++++--- .../Microsoft.Extensions.Azure/samples/Startup.cs | 4 +--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/sdk/core/Azure.Core/src/AsyncCollection.cs b/sdk/core/Azure.Core/src/AsyncCollection.cs index 783f9b3de4451..593ff693f311d 100644 --- a/sdk/core/Azure.Core/src/AsyncCollection.cs +++ b/sdk/core/Azure.Core/src/AsyncCollection.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Threading; +using System.Threading.Tasks; namespace Azure { @@ -68,7 +69,7 @@ public abstract IAsyncEnumerable> ByPage( /// An async sequence of values. public virtual async IAsyncEnumerator> GetAsyncEnumerator(CancellationToken cancellationToken = default) { - await foreach (Page page in ByPage()) + await foreach (Page page in ByPage().ConfigureAwait(false).WithCancellation(cancellationToken)) { foreach (T value in page.Values) { diff --git a/sdk/core/Azure.Core/src/Shared/PageResponseEnumerator.cs b/sdk/core/Azure.Core/src/Shared/PageResponseEnumerator.cs index 6d01f6657c811..3dd6c47c9ebcf 100644 --- a/sdk/core/Azure.Core/src/Shared/PageResponseEnumerator.cs +++ b/sdk/core/Azure.Core/src/Shared/PageResponseEnumerator.cs @@ -26,15 +26,20 @@ public static IEnumerable> CreateEnumerable(Func> } public static AsyncCollection CreateAsyncEnumerable(Func>> pageFunc) + { + return new FuncAsyncCollection((continuationToken, pageSizeHint) => pageFunc(continuationToken)); + } + + public static AsyncCollection CreateAsyncEnumerable(Func>> pageFunc) { return new FuncAsyncCollection(pageFunc); } internal class FuncAsyncCollection: AsyncCollection { - private readonly Func>> _pageFunc; + private readonly Func>> _pageFunc; - public FuncAsyncCollection(Func>> pageFunc) + public FuncAsyncCollection(Func>> pageFunc) { _pageFunc = pageFunc; } @@ -43,7 +48,7 @@ public override async IAsyncEnumerable> ByPage(string continuationToken { do { - Page pageResponse = await _pageFunc(continuationToken).ConfigureAwait(false); + Page pageResponse = await _pageFunc(continuationToken, pageSizeHint).ConfigureAwait(false); yield return pageResponse; continuationToken = pageResponse.ContinuationToken; } while (continuationToken != null); diff --git a/sdk/core/Microsoft.Extensions.Azure/samples/Startup.cs b/sdk/core/Microsoft.Extensions.Azure/samples/Startup.cs index 047b816886d24..683a82aebd25c 100644 --- a/sdk/core/Microsoft.Extensions.Azure/samples/Startup.cs +++ b/sdk/core/Microsoft.Extensions.Azure/samples/Startup.cs @@ -25,7 +25,7 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public IServiceProvider ConfigureServices(IServiceCollection services) + public void ConfigureServices(IServiceCollection services) { // Registering policy to use in ConfigureDefaults later services.AddSingleton(); @@ -53,8 +53,6 @@ public IServiceProvider ConfigureServices(IServiceCollection services) builder.AddBlobServiceClient(Configuration.GetSection("Storage")) .WithVersion(BlobClientOptions.ServiceVersion.V2018_11_09); }); - - return services.BuildServiceProvider(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. From bfc6291e768b20863be868608c471b15eeede88e Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Wed, 31 Jul 2019 10:13:05 -0700 Subject: [PATCH 3/3] Test reactions --- .../tests/TestFramework/AsyncOnlyAttribute.cs | 38 +++++++++++++++++++ .../UseSyncMethodsInterceptor.cs | 5 +++ .../tests/ContainerClientTests.cs | 12 +++--- .../tests/ServiceClientTests.cs | 2 + .../tests/DirectoryClientTests.cs | 1 + .../tests/FileClientTests.cs | 13 ++----- .../FileClientTests/ListHandles.json | 2 +- .../FileClientTests/ListHandlesAsync.json | 2 +- .../tests/ServiceClientTests.cs | 2 + 9 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 sdk/core/Azure.Core/tests/TestFramework/AsyncOnlyAttribute.cs diff --git a/sdk/core/Azure.Core/tests/TestFramework/AsyncOnlyAttribute.cs b/sdk/core/Azure.Core/tests/TestFramework/AsyncOnlyAttribute.cs new file mode 100644 index 0000000000000..f65c247310cf8 --- /dev/null +++ b/sdk/core/Azure.Core/tests/TestFramework/AsyncOnlyAttribute.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using NUnit.Framework.Interfaces; +using NUnit.Framework.Internal; + +namespace Azure.Core.Testing +{ + + [AttributeUsage(AttributeTargets.Method|AttributeTargets.Class|AttributeTargets.Assembly, AllowMultiple=false, Inherited=false)] + public class AsyncOnlyAttribute : NUnitAttribute, IApplyToTest + { + #region IApplyToTest members + + /// + /// Modifies a test by marking it as Ignored. + /// + /// The test to modify + public void ApplyToTest(Test test) + { + if (test.RunState != RunState.NotRunnable) + { + // This is an unfortunate implementation but it's the only one I was able to figure out + string testParameters = test.FullName.Substring(test.ClassName.Length); + if (testParameters.StartsWith("(False")) + { + test.RunState = RunState.Ignored; + } + } + } + + #endregion + } +} diff --git a/sdk/core/Azure.Core/tests/TestFramework/UseSyncMethodsInterceptor.cs b/sdk/core/Azure.Core/tests/TestFramework/UseSyncMethodsInterceptor.cs index 680096694f4c6..e577c9012c8cf 100644 --- a/sdk/core/Azure.Core/tests/TestFramework/UseSyncMethodsInterceptor.cs +++ b/sdk/core/Azure.Core/tests/TestFramework/UseSyncMethodsInterceptor.cs @@ -128,6 +128,11 @@ public override async IAsyncEnumerable> ByPage(string continuationToken throw new InvalidOperationException("Calling ByPage with a continuationToken is not supported in the sync mode"); } + if (pageSizeHint != null) + { + throw new InvalidOperationException("Calling ByPage with a pageSizeHint is not supported in the sync mode"); + } + foreach (Response response in _enumerable) { yield return new Page(new [] { response.Value}, null, response.GetRawResponse()); diff --git a/sdk/storage/Azure.Storage.Blobs/tests/ContainerClientTests.cs b/sdk/storage/Azure.Storage.Blobs/tests/ContainerClientTests.cs index 35f974d0d55f2..66c5c4f79bafd 100644 --- a/sdk/storage/Azure.Storage.Blobs/tests/ContainerClientTests.cs +++ b/sdk/storage/Azure.Storage.Blobs/tests/ContainerClientTests.cs @@ -228,7 +228,7 @@ await TestHelper.AssertExpectedExceptionAsync( [Test] public async Task DeleteAsync_AccessConditions() { - var garbageLeaseId = this.GetGarbageLeaseId(); + var garbageLeaseId = this.GetGarbageLeaseId(); foreach (var parameters in this.AccessConditions_Data) { // Arrange @@ -699,7 +699,7 @@ public async Task RenewLeaseAsync() var duration = 15; var leaseResponse = await container.GetLeaseClient(id).AcquireAsync( - duration: duration); + duration: duration); // Act var renewResponse = await container.GetLeaseClient(leaseResponse.Value.LeaseId).RenewAsync(); @@ -1150,6 +1150,7 @@ public async Task ListBlobsFlatSegmentAsync() } [Test] + [AsyncOnly] public async Task ListBlobsFlatSegmentAsync_MaxResults() { using (this.GetNewContainer(out var container)) @@ -1159,12 +1160,12 @@ public async Task ListBlobsFlatSegmentAsync_MaxResults() // Act var page = await container.GetBlobsAsync().ByPage(pageSizeHint: 2).FirstAsync(); - + // Assert Assert.AreEqual(2, page.Values.Count); } } - + [Test] public async Task ListBlobsFlatSegmentAsync_Metadata() { @@ -1351,6 +1352,7 @@ public async Task ListBlobsHierarchySegmentAsync() } [Test] + [AsyncOnly] public async Task ListBlobsHierarchySegmentAsync_MaxResults() { using (this.GetNewContainer(out var container)) @@ -1368,7 +1370,7 @@ public async Task ListBlobsHierarchySegmentAsync_MaxResults() Assert.AreEqual(2, page.Values.Count); } } - + [Test] public async Task ListBlobsHierarchySegmentAsync_Metadata() { diff --git a/sdk/storage/Azure.Storage.Blobs/tests/ServiceClientTests.cs b/sdk/storage/Azure.Storage.Blobs/tests/ServiceClientTests.cs index 04f8bccf7ac8a..13af5992bc12e 100644 --- a/sdk/storage/Azure.Storage.Blobs/tests/ServiceClientTests.cs +++ b/sdk/storage/Azure.Storage.Blobs/tests/ServiceClientTests.cs @@ -81,6 +81,7 @@ public async Task ListContainersSegmentAsync_Marker() } [Test] + [AsyncOnly] public async Task ListContainersSegmentAsync_MaxResults() { var service = this.GetServiceClient_SharedKey(); @@ -138,6 +139,7 @@ public async Task ListContainersSegmentAsync_Metadata() } [Test] + [AsyncOnly] public async Task ListContainersSegmentAsync_Error() { // Arrange diff --git a/sdk/storage/Azure.Storage.Files/tests/DirectoryClientTests.cs b/sdk/storage/Azure.Storage.Files/tests/DirectoryClientTests.cs index ba5988657b7d4..59ad9e98b3b8f 100644 --- a/sdk/storage/Azure.Storage.Files/tests/DirectoryClientTests.cs +++ b/sdk/storage/Azure.Storage.Files/tests/DirectoryClientTests.cs @@ -253,6 +253,7 @@ await TestHelper.AssertExpectedExceptionAsync( } [Test] + [AsyncOnly] public async Task ListHandles() { // Arrange diff --git a/sdk/storage/Azure.Storage.Files/tests/FileClientTests.cs b/sdk/storage/Azure.Storage.Files/tests/FileClientTests.cs index 301ecc9bac179..7e21e26fff51e 100644 --- a/sdk/storage/Azure.Storage.Files/tests/FileClientTests.cs +++ b/sdk/storage/Azure.Storage.Files/tests/FileClientTests.cs @@ -508,7 +508,7 @@ await file.UploadRangeAsync( // Act var response = await file.DownloadAsync(range: new HttpRange(Constants.KB, data.LongLength)); - + // Assert Assert.AreEqual(data.Length, response.Value.ContentLength); var actual = new MemoryStream(); @@ -548,7 +548,7 @@ public async Task DownloadAsync_WithUnreliableConnection() { await fileFaulty.UploadRangeAsync( writeType: FileRangeWriteType.Update, - range: new HttpRange(offset, dataSize), + range: new HttpRange(offset, dataSize), content: stream); } @@ -661,7 +661,7 @@ public async Task UploadRangeAsync_WithUnreliableConnection() var result = await fileFaulty.UploadRangeAsync( writeType: FileRangeWriteType.Update, range: new HttpRange(offset, dataSize), - content: stream, + content: stream, progressHandler: progressHandler); Assert.IsNotNull(result); @@ -690,12 +690,7 @@ public async Task ListHandles() using (this.GetNewFile(out var file)) { // Act - var handles = - (await file.GetHandlesAsync() - .ByPage(pageSizeHint: 5) - .ToListAsync()) - .SelectMany(p => p.Values) - .ToList(); + var handles = await file.GetHandlesAsync().ToListAsync(); // Assert Assert.AreEqual(0, handles.Count); diff --git a/sdk/storage/Azure.Storage.Files/tests/SessionRecords/FileClientTests/ListHandles.json b/sdk/storage/Azure.Storage.Files/tests/SessionRecords/FileClientTests/ListHandles.json index 9fcd57a84ba10..af99745e5a010 100644 --- a/sdk/storage/Azure.Storage.Files/tests/SessionRecords/FileClientTests/ListHandles.json +++ b/sdk/storage/Azure.Storage.Files/tests/SessionRecords/FileClientTests/ListHandles.json @@ -99,7 +99,7 @@ "ResponseBody": [] }, { - "RequestUri": "http:\u002f\u002fstorageteglazatesting.file.core.windows.net\u002ftest-share-68a732f4-c35d-d481-a001-24fe394833c2\u002ftest-directory-c0c37426-5a57-3ac5-9ccd-428aee0578a8\u002ftest-file-c5d79c40-7385-826b-eeeb-02f884e9731e?comp=listhandles\u0026maxresults=5", + "RequestUri": "http:\u002f\u002fstorageteglazatesting.file.core.windows.net\u002ftest-share-68a732f4-c35d-d481-a001-24fe394833c2\u002ftest-directory-c0c37426-5a57-3ac5-9ccd-428aee0578a8\u002ftest-file-c5d79c40-7385-826b-eeeb-02f884e9731e?comp=listhandles", "RequestMethod": "GET", "RequestHeaders": { "Authorization": "Sanitized", diff --git a/sdk/storage/Azure.Storage.Files/tests/SessionRecords/FileClientTests/ListHandlesAsync.json b/sdk/storage/Azure.Storage.Files/tests/SessionRecords/FileClientTests/ListHandlesAsync.json index f0581fdd357e5..09e0b4b9c43b7 100644 --- a/sdk/storage/Azure.Storage.Files/tests/SessionRecords/FileClientTests/ListHandlesAsync.json +++ b/sdk/storage/Azure.Storage.Files/tests/SessionRecords/FileClientTests/ListHandlesAsync.json @@ -99,7 +99,7 @@ "ResponseBody": [] }, { - "RequestUri": "http:\u002f\u002fstorageteglazatesting.file.core.windows.net\u002ftest-share-a9daa292-8626-4059-fd38-24cc282d84cc\u002ftest-directory-c11c111d-9767-4f63-e5b4-02fbf11e3525\u002ftest-file-17afbdb5-c210-8565-363e-1d438bdc20a6?comp=listhandles\u0026maxresults=5", + "RequestUri": "http:\u002f\u002fstorageteglazatesting.file.core.windows.net\u002ftest-share-a9daa292-8626-4059-fd38-24cc282d84cc\u002ftest-directory-c11c111d-9767-4f63-e5b4-02fbf11e3525\u002ftest-file-17afbdb5-c210-8565-363e-1d438bdc20a6?comp=listhandles", "RequestMethod": "GET", "RequestHeaders": { "Authorization": "Sanitized", diff --git a/sdk/storage/Azure.Storage.Queues/tests/ServiceClientTests.cs b/sdk/storage/Azure.Storage.Queues/tests/ServiceClientTests.cs index e3b81f989cdbd..b130e8f22c964 100644 --- a/sdk/storage/Azure.Storage.Queues/tests/ServiceClientTests.cs +++ b/sdk/storage/Azure.Storage.Queues/tests/ServiceClientTests.cs @@ -53,6 +53,7 @@ public async Task GetQueuesAsync_Marker() } [Test] + [AsyncOnly] public async Task GetQueuesAsync_MaxResults() { var service = this.GetServiceClient_SharedKey(); @@ -103,6 +104,7 @@ public async Task GetQueuesAsync_Metadata() } [Test] + [AsyncOnly] public async Task GetQueuesAsync_Error() { var service = this.GetServiceClient_SharedKey();