From 7c79d8a0930c93917b3c1029ec2936423719ae15 Mon Sep 17 00:00:00 2001 From: Deepak Shankargouda Date: Fri, 14 Oct 2016 13:23:24 -0700 Subject: [PATCH] Moving ClientRuntime to azure dotnet sdk repo (#2401) * Moving ClientRuntime to azure dotnet sdk repo * adding tests * temp * fixing ci * undoing commenting * undoing 2 * merged latest changes * using win sdk exec paths * Unused comments * fixing assemblyinfo * Fixing Packaging of ClientRuntime projects * minor fix * adding msging * verbose pack * Fixing signing ci builds * tweak build script * synced changes from AutoRest/master and fixed build proj * removed superflous brace * fixing test? * adding slns for individual client runtime projects to allow individual build/publish --- build.proj | 38 +- global.json | 1 + src/ClientRuntime/ClientRuntime.sln | 64 + .../ActiveDirectoryClientSettings.cs | 128 + .../ActiveDirectoryServiceSettings.cs | 88 + .../ApplicationTokenProvider.cs | 560 ++++ .../AuthenticationException.cs | 88 + .../CertificateAuthenticationProvider.cs | 39 + .../GlobalSuppressions.cs | 55 + .../IApplicationAuthenticationProvider.cs | 24 + .../IUserCredentialProvider.cs | 21 + ...MemoryApplicationAuthenticationProvider.cs | 66 + ...est.ClientRuntime.Azure.Authentication.sln | 52 + ...t.ClientRuntime.Azure.Authentication.xproj | 23 + .../Properties/AssemblyInfo.cs | 23 + .../Properties/Resources.Designer.cs | 89 + .../Properties/Resources.resx | 129 + .../Settings.SourceAnalysis | 196 ++ .../UserTokenProvider.cs | 607 +++++ .../project.json | 48 + .../ActiveDirectoryCredentialsTest.cs | 286 ++ .../ActiveDirectorySettingsTest.cs | 140 + .../CloudErrorJsonConverterTest.cs | 97 + .../Fakes/FakeServiceClient.cs | 46 + .../Fakes/FakeServiceClientWithCredentials.cs | 152 ++ .../Fakes/PlaybackTestHandler.cs | 42 + .../Fakes/RecordedDelegatingHandler.cs | 80 + .../LongRunningOperationsTest.cs | 1440 ++++++++++ ...osoft.Rest.ClientRuntime.Azure.Tests.xproj | 23 + .../ODataTests.cs | 455 ++++ .../Properties/AssemblyInfo.cs | 20 + .../ResourceJsonConverterTest.cs | 502 ++++ .../Sample/GenericResource.cs | 61 + .../Sample/JobInformation.cs | 114 + .../Sample/JobProperties.cs | 61 + .../Sample/Page.cs | 50 + .../Sample/RedisManagementClient.cs | 2317 +++++++++++++++++ .../Sample/SampleResource.cs | 170 ++ .../Sample/USql.cs | 102 + .../TokenCloudCredentialsTest.cs | 62 + .../project.json | 30 + .../AzureAsyncOperation.cs | 81 + .../AzureClientExtensions.cs | 657 +++++ .../AzureOperationResponse.cs | 62 + .../ClientRequestTrackingHandler.cs | 66 + .../CloudError.cs | 41 + .../CloudErrorJsonConverter.cs | 75 + .../CloudException.cs | 59 + .../GlobalSuppressions.cs | 23 + .../IAzureClient.cs | 45 + .../IPage.cs | 21 + .../IResource.cs | 12 + .../JsonSerializerExtensions.cs | 42 + .../Microsoft.Rest.ClientRuntime.Azure.sln | 46 + .../Microsoft.Rest.ClientRuntime.Azure.xproj | 23 + .../OData/FilterString.cs | 44 + .../OData/ODataMethodAttribute.cs | 28 + .../OData/ODataQuery.cs | 175 ++ .../OData/UrlExpressionVisitor.cs | 484 ++++ .../PollingState.cs | 217 ++ .../Properties/AssemblyInfo.cs | 22 + .../Properties/Resources.Designer.cs | 315 +++ .../Properties/Resources.resx | 205 ++ .../ResourceJsonConverter.cs | 200 ++ .../Settings.SourceAnalysis | 196 ++ .../project.json | 83 + .../EtwTracingInterceptor.cs | 113 + .../HttpOperationEventSource.cs | 114 + .../Microsoft.Rest.ClientRuntime.Etw.xproj | 23 + .../Properties/AssemblyInfo.cs | 22 + .../README.md | 11 + .../project.json | 34 + .../Log4NetTracingInterceptor.cs | 141 + ...Microsoft.Rest.ClientRuntime.Log4Net.xproj | 23 + .../Properties/AssemblyInfo.cs | 22 + .../README.md | 42 + .../project.json | 38 + .../AddHeaderResponseDelegatingHandler.cs | 28 + .../Fakes/AppenderDelegatingHandler.cs | 29 + .../Fakes/BadResponseDelegatingHandler.cs | 37 + .../Fakes/FakeHttpHandler.cs | 38 + .../Fakes/FakeServiceClient.cs | 125 + .../Fakes/FakeServiceClientWithCredentials.cs | 125 + .../Fakes/MirrorDelegatingHandler.cs | 31 + .../Fakes/MirrorMessageHandler.cs | 39 + .../Fakes/RecordedDelegatingHandler.cs | 79 + .../JsonSerializationTests.cs | 456 ++++ .../JsonTransformationConverterTest.cs | 236 ++ .../Microsoft.Rest.ClientRuntime.Tests.xproj | 24 + .../Properties/AssemblyInfo.cs | 19 + .../Resources/Animal.cs | 86 + .../Resources/DateTestObject.cs | 41 + .../Resources/SampleResource.cs | 181 ++ .../ServiceClientTests.cs | 236 ++ .../Tracing/CloudTracingExtensionsTest.cs | 132 + .../DefaultHttpErrorDetectionStrategyTests.cs | 42 + .../GeneralRetryPolicyTests.cs | 134 + .../RetryConditionTest.cs | 24 + .../ValidationExceptionTests.cs | 147 ++ .../project.json | 30 + .../EtwTracingInterceptorTest.cs | 267 ++ .../Log4NetTracingInterceptorTest.cs | 184 ++ ...oft.Rest.ClientRuntime.Tracing.Tests.xproj | 19 + .../Properties/AssemblyInfo.cs | 18 + .../app.config | 26 + .../project.json | 32 + .../BasicAuthenticationCredentials.cs | 53 + .../CertificateCredentials.cs | 70 + .../GlobalSuppressions.cs | 5 + .../HttpExtensions.cs | 226 ++ .../HttpMessageWrapper.cs | 58 + .../HttpOperationException.cs | 93 + .../HttpOperationResponse.cs | 139 + .../HttpRequestMessageWrapper.cs | 58 + .../HttpResponseMessageWrapper.cs | 45 + .../IServiceClientTracingInterceptor.cs | 69 + .../IServiceOperations.cs | 18 + .../ITokenProvider.cs | 20 + .../Microsoft.Rest.ClientRuntime.sln | 34 + .../Microsoft.Rest.ClientRuntime.xproj | 23 + .../Properties/AssemblyInfo.cs | 22 + .../Properties/Resources.Designer.cs | 323 +++ .../Properties/Resources.resx | 207 ++ .../RestException.cs | 55 + .../RetryDelegatingHandler.cs | 152 ++ .../Serialization/Base64UrlJsonConverter.cs | 94 + .../Serialization/DateJsonConverter.cs | 47 + .../DateTimeRfc1123JsonConverter.cs | 65 + .../Serialization/Iso8601TimeSpanConverter.cs | 75 + .../Serialization/JsonConverterHelper.cs | 108 + .../JsonTransformationAttribute.cs | 16 + .../PolymorphicDeserializeJsonConverter.cs | 89 + .../Serialization/PolymorphicJsonConverter.cs | 50 + .../PolymorphicSerializeJsonConverter.cs | 100 + .../ReadOnlyJsonContractResolver.cs | 38 + .../Serialization/SafeJsonConvert.cs | 90 + .../TransformationJsonConverter.cs | 189 ++ .../Serialization/UnixTimeJsonConverter.cs | 77 + .../SerializationException.cs | 95 + .../ServiceClient.cs | 416 +++ .../ServiceClientCredentials.cs | 39 + .../ServiceClientTracing.cs | 247 ++ .../StringTokenProvider.cs | 49 + .../TokenCredentials.cs | 119 + .../TransientFaultHandling/AsyncExecution.cs | 239 ++ .../ExponentialBackoffRetryStrategy.cs | 129 + .../FixedIntervalRetryStrategy.cs | 102 + .../TransientFaultHandling/Guard.cs | 74 + .../HttpRequestWithStatusException.cs | 54 + .../HttpStatusCodeErrorDetectionStrategy.cs | 40 + .../ITransientErrorDetectionStrategy.cs | 23 + .../IncrementalRetryStrategy.cs | 98 + .../TransientFaultHandling/RetryCondition.cs | 33 + .../TransientFaultHandling/RetryManager.cs | 236 ++ .../RetryPolicy.Generic.cs | 73 + .../TransientFaultHandling/RetryPolicy.cs | 280 ++ .../TransientFaultHandling/RetryStrategy.cs | 69 + .../RetryingEventArgs.cs | 45 + .../TransientErrorIgnoreStrategy.cs | 23 + .../TypeConversion.cs | 44 + .../ValidationException.cs | 119 + .../ValidationRules.cs | 27 + .../Microsoft.Rest.ClientRuntime/project.json | 88 + src/ClientRuntime/global.json | 5 + src/KeyVault/global.json | 1 + .../Authorization/global.json | 2 +- src/ResourceManagement/Batch/global.json | 2 +- src/ResourceManagement/Cdn/global.json | 2 +- .../CognitiveServices/global.json | 2 +- src/ResourceManagement/Compute/global.json | 2 +- .../DataLake.Analytics/global.json | 2 +- .../DataLake.Store/global.json | 2 +- .../DevTestLabs/global.json | 2 +- src/ResourceManagement/Dns/global.json | 2 +- src/ResourceManagement/EventHub/global.json | 2 +- src/ResourceManagement/Graph.RBAC/global.json | 2 +- src/ResourceManagement/Insights/global.json | 2 +- src/ResourceManagement/IotHub/global.json | 2 +- .../KeyVaultManagement/global.json | 2 +- src/ResourceManagement/Logic/global.json | 2 +- .../MachineLearning/global.json | 2 +- src/ResourceManagement/Media/global.json | 2 +- src/ResourceManagement/Network/global.json | 2 +- .../NotificationHubs/global.json | 2 +- .../PowerBIEmbedded/global.json | 2 +- src/ResourceManagement/RedisCache/global.json | 2 +- src/ResourceManagement/Resource/global.json | 2 +- src/ResourceManagement/Scheduler/global.json | 2 +- .../ServerManagement/global.json | 2 +- src/ResourceManagement/ServiceBus/global.json | 2 +- src/ResourceManagement/Storage/global.json | 2 +- .../TrafficManager/global.json | 2 +- src/ResourceManagement/WebSite/global.json | 2 +- src/Search/global.json | 2 +- src/TestFramework/global.json | 2 +- 195 files changed, 20762 insertions(+), 32 deletions(-) create mode 100644 src/ClientRuntime/ClientRuntime.sln create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/ActiveDirectoryClientSettings.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/ActiveDirectoryServiceSettings.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/ApplicationTokenProvider.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/AuthenticationException.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/CertificateAuthenticationProvider.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/GlobalSuppressions.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/IApplicationAuthenticationProvider.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/IUserCredentialProvider.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/MemoryApplicationAuthenticationProvider.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Microsoft.Rest.ClientRuntime.Azure.Authentication.sln create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Microsoft.Rest.ClientRuntime.Azure.Authentication.xproj create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Properties/AssemblyInfo.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Properties/Resources.Designer.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Properties/Resources.resx create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Settings.SourceAnalysis create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/UserTokenProvider.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/project.json create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ActiveDirectoryCredentialsTest.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ActiveDirectorySettingsTest.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/CloudErrorJsonConverterTest.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/FakeServiceClient.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/FakeServiceClientWithCredentials.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/PlaybackTestHandler.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/RecordedDelegatingHandler.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/LongRunningOperationsTest.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Microsoft.Rest.ClientRuntime.Azure.Tests.xproj create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ODataTests.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Properties/AssemblyInfo.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ResourceJsonConverterTest.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/GenericResource.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/JobInformation.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/JobProperties.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/Page.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/RedisManagementClient.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/SampleResource.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/USql.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/TokenCloudCredentialsTest.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/project.json create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/AzureAsyncOperation.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/AzureClientExtensions.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/AzureOperationResponse.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/ClientRequestTrackingHandler.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/CloudError.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/CloudErrorJsonConverter.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/CloudException.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/GlobalSuppressions.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/IAzureClient.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/IPage.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/IResource.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/JsonSerializerExtensions.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Microsoft.Rest.ClientRuntime.Azure.sln create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Microsoft.Rest.ClientRuntime.Azure.xproj create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/FilterString.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/ODataMethodAttribute.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/ODataQuery.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/UrlExpressionVisitor.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/PollingState.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Properties/AssemblyInfo.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Properties/Resources.Designer.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Properties/Resources.resx create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/ResourceJsonConverter.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Settings.SourceAnalysis create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/project.json create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/EtwTracingInterceptor.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/HttpOperationEventSource.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/Microsoft.Rest.ClientRuntime.Etw.xproj create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/Properties/AssemblyInfo.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/README.md create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/project.json create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/Log4NetTracingInterceptor.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/Microsoft.Rest.ClientRuntime.Log4Net.xproj create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/Properties/AssemblyInfo.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/README.md create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/project.json create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/AddHeaderResponseDelegatingHandler.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/AppenderDelegatingHandler.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/BadResponseDelegatingHandler.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/FakeHttpHandler.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/FakeServiceClient.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/FakeServiceClientWithCredentials.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/MirrorDelegatingHandler.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/MirrorMessageHandler.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/RecordedDelegatingHandler.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/JsonSerializationTests.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/JsonTransformationConverterTest.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Microsoft.Rest.ClientRuntime.Tests.xproj create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Properties/AssemblyInfo.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Resources/Animal.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Resources/DateTestObject.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Resources/SampleResource.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/ServiceClientTests.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Tracing/CloudTracingExtensionsTest.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/TransientFaultHandling/DefaultHttpErrorDetectionStrategyTests.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/TransientFaultHandling/GeneralRetryPolicyTests.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/TransientFaultHandling/RetryConditionTest.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/ValidationExceptionTests.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/project.json create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/EtwTracingInterceptorTest.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/Log4NetTracingInterceptorTest.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/Microsoft.Rest.ClientRuntime.Tracing.Tests.xproj create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/Properties/AssemblyInfo.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/app.config create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/project.json create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/BasicAuthenticationCredentials.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/CertificateCredentials.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/GlobalSuppressions.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpExtensions.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpMessageWrapper.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpOperationException.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpOperationResponse.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpRequestMessageWrapper.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpResponseMessageWrapper.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/IServiceClientTracingInterceptor.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/IServiceOperations.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/ITokenProvider.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Microsoft.Rest.ClientRuntime.sln create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Microsoft.Rest.ClientRuntime.xproj create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Properties/AssemblyInfo.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Properties/Resources.Designer.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Properties/Resources.resx create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/RestException.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/RetryDelegatingHandler.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/Base64UrlJsonConverter.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/DateJsonConverter.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/DateTimeRfc1123JsonConverter.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/Iso8601TimeSpanConverter.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/JsonConverterHelper.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/JsonTransformationAttribute.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/PolymorphicDeserializeJsonConverter.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/PolymorphicJsonConverter.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/PolymorphicSerializeJsonConverter.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/ReadOnlyJsonContractResolver.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/SafeJsonConvert.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/TransformationJsonConverter.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/UnixTimeJsonConverter.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/SerializationException.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClient.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClientCredentials.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClientTracing.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/StringTokenProvider.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TokenCredentials.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/AsyncExecution.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/ExponentialBackoffRetryStrategy.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/FixedIntervalRetryStrategy.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/Guard.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/HttpRequestWithStatusException.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/HttpStatusCodeErrorDetectionStrategy.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/ITransientErrorDetectionStrategy.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/IncrementalRetryStrategy.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryCondition.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryManager.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryPolicy.Generic.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryPolicy.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryStrategy.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryingEventArgs.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/TransientErrorIgnoreStrategy.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/TypeConversion.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/ValidationException.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/ValidationRules.cs create mode 100644 src/ClientRuntime/Microsoft.Rest.ClientRuntime/project.json create mode 100644 src/ClientRuntime/global.json diff --git a/build.proj b/build.proj index 144e3f8d638de..97bcabb6cb057 100644 --- a/build.proj +++ b/build.proj @@ -68,6 +68,8 @@ + + @@ -77,6 +79,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -109,13 +139,15 @@ + - + <_ExtraPropertyList>CodeSign=$(CodeSign) <_TemporaryNetCoreFeeds>-s https://api.nuget.org/v3/index.json -s https://dotnet.myget.org/F/cli-deps/api/v3/index.json + - + + diff --git a/global.json b/global.json index a09ee6e75f838..874b12b087dde 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,6 @@ { "projects": [ + "src/ClientRuntime", "src/KeyVault", "src/TestFramework/Microsoft.Azure.Test.HttpRecorder", "src/TestFramework/Microsoft.Rest.ClientRuntime.Azure.TestFramework", diff --git a/src/ClientRuntime/ClientRuntime.sln b/src/ClientRuntime/ClientRuntime.sln new file mode 100644 index 0000000000000..a46ceceadc5fc --- /dev/null +++ b/src/ClientRuntime/ClientRuntime.sln @@ -0,0 +1,64 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime", "Microsoft.Rest.ClientRuntime\Microsoft.Rest.ClientRuntime.xproj", "{EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Azure.Authentication", "Microsoft.Rest.ClientRuntime.Azure.Authentication\Microsoft.Rest.ClientRuntime.Azure.Authentication.xproj", "{6319205D-BBFC-4150-BEAE-31B1C9B911DD}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Azure", "Microsoft.Rest.ClientRuntime.Azure\Microsoft.Rest.ClientRuntime.Azure.xproj", "{D5296EAB-C13E-4A88-9532-BD0677D18EC9}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Azure.Tests", "Microsoft.Rest.ClientRuntime.Azure.Tests\Microsoft.Rest.ClientRuntime.Azure.Tests.xproj", "{3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Etw", "Microsoft.Rest.ClientRuntime.Etw\Microsoft.Rest.ClientRuntime.Etw.xproj", "{218D7297-8254-4C70-9C04-33C3D5EE9709}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Log4Net", "Microsoft.Rest.ClientRuntime.Log4Net\Microsoft.Rest.ClientRuntime.Log4Net.xproj", "{348E414F-101A-4939-99FF-2C994A965A89}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Tests", "Microsoft.Rest.ClientRuntime.Tests\Microsoft.Rest.ClientRuntime.Tests.xproj", "{F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Tracing.Tests", "Microsoft.Rest.ClientRuntime.Tracing.Tests\Microsoft.Rest.ClientRuntime.Tracing.Tests.xproj", "{52C61F15-BF86-41DC-93D1-05D3DA70F032}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Release|Any CPU.Build.0 = Release|Any CPU + {6319205D-BBFC-4150-BEAE-31B1C9B911DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6319205D-BBFC-4150-BEAE-31B1C9B911DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6319205D-BBFC-4150-BEAE-31B1C9B911DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6319205D-BBFC-4150-BEAE-31B1C9B911DD}.Release|Any CPU.Build.0 = Release|Any CPU + {D5296EAB-C13E-4A88-9532-BD0677D18EC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5296EAB-C13E-4A88-9532-BD0677D18EC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5296EAB-C13E-4A88-9532-BD0677D18EC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5296EAB-C13E-4A88-9532-BD0677D18EC9}.Release|Any CPU.Build.0 = Release|Any CPU + {3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}.Release|Any CPU.Build.0 = Release|Any CPU + {218D7297-8254-4C70-9C04-33C3D5EE9709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {218D7297-8254-4C70-9C04-33C3D5EE9709}.Debug|Any CPU.Build.0 = Debug|Any CPU + {218D7297-8254-4C70-9C04-33C3D5EE9709}.Release|Any CPU.ActiveCfg = Release|Any CPU + {218D7297-8254-4C70-9C04-33C3D5EE9709}.Release|Any CPU.Build.0 = Release|Any CPU + {348E414F-101A-4939-99FF-2C994A965A89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {348E414F-101A-4939-99FF-2C994A965A89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {348E414F-101A-4939-99FF-2C994A965A89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {348E414F-101A-4939-99FF-2C994A965A89}.Release|Any CPU.Build.0 = Release|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Release|Any CPU.Build.0 = Release|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/ActiveDirectoryClientSettings.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/ActiveDirectoryClientSettings.cs new file mode 100644 index 0000000000000..f786745b565c8 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/ActiveDirectoryClientSettings.cs @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.IdentityModel.Clients.ActiveDirectory; +using Microsoft.Rest.ClientRuntime.Azure.Authentication.Properties; + +namespace Microsoft.Rest.Azure.Authentication +{ + /// + /// Settings for authentication with an Azure or Azure Stack service using Active Directory. + /// + public sealed class ActiveDirectoryClientSettings + { + /// + /// Query string allowing use of cookies in user login dialog + /// + public const string EnableEbdMagicCookie = "site_id=501358&display=popup"; + +#if !PORTABLE + /// + /// Gets or sets prompt behavior. + /// + public PromptBehavior PromptBehavior { get; set; } +#endif + + /// + /// Gets or sets owner window. + /// + public object OwnerWindow { get; set; } + + /// + /// Additional query parameters sent with the AD request + /// + public string AdditionalQueryParameters { get; set; } + + /// + /// The active directory client id for this application. + /// + public string ClientId { get; set; } + + /// + /// The client redirect uri associated with this application. + /// + public Uri ClientRedirectUri { get; set; } + + /// + /// Initializes default active directory dialog parameters. + /// + public ActiveDirectoryClientSettings() + { +#if !PORTABLE + this.PromptBehavior = PromptBehavior.Auto; +#endif + this.AdditionalQueryParameters = EnableEbdMagicCookie; + } + + /// + /// Initializes active directory client settings with the application specific properties + /// for client id and client redirect uri. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory client id. + /// The client redirect uri defined for this application in active directory. + public ActiveDirectoryClientSettings(string clientId, Uri clientRedirectUri) : this() + { + this.ClientId = clientId; + this.ClientRedirectUri = clientRedirectUri; + } + + /// + /// Returns active directory cient settings that suppress user credential prompts. Authentication + /// will succeed if and only if previous authentication results are cached in the TokenCache or + /// client cookies. + /// + /// The client Id associated with this active directory application. + /// The client redirect Uri associated with this active directory application. + /// active directory client settings that suppress user credential prompts. + public static ActiveDirectoryClientSettings UseCacheOrCookiesOnly(string clientId, Uri clientRedirectUri) + { + return new ActiveDirectoryClientSettings(clientId, clientRedirectUri) + { +#if !PORTABLE + PromptBehavior = PromptBehavior.Never, +#endif + }; + } + + /// + /// Returns active directory client settings that prompt the user for credentials only when + /// no matching cookies or cached tokens are found. Authentication will succeed if valid cookies + /// or cached tokens are found, or, if no valid tokens are found, the user enters valid + /// active directory user credentials. + /// + /// The client Id associated with this active directory application. + /// The client redirect Uri associated with this active directory application. + /// Settings that prefer cached tokens or cookies over user prompting. + public static ActiveDirectoryClientSettings UseCacheCookiesOrPrompt(string clientId, Uri clientRedirectUri) + { + return new ActiveDirectoryClientSettings(clientId , clientRedirectUri) + { +#if !PORTABLE + PromptBehavior = PromptBehavior.Auto, +#endif + }; + } + + /// + /// Ignore authentication cookies or cached tokens and force the user to enter valid credentials. + /// Authentication will succeed if and only if the user enters valid credentials. + /// + /// The client Id associated with this active directory application. + /// The client redirect Uri associated with this active directory application. + /// Settings that require the user to input credentials, + public static ActiveDirectoryClientSettings UsePromptOnly(string clientId, Uri clientRedirectUri) + { + return new ActiveDirectoryClientSettings(clientId, clientRedirectUri) + { +#if !PORTABLE + PromptBehavior = PromptBehavior.Always +#endif + }; + } + + + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/ActiveDirectoryServiceSettings.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/ActiveDirectoryServiceSettings.cs new file mode 100644 index 0000000000000..3fe363ef928ca --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/ActiveDirectoryServiceSettings.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.IdentityModel.Clients.ActiveDirectory; +using Microsoft.Rest.ClientRuntime.Azure.Authentication.Properties; + +namespace Microsoft.Rest.Azure.Authentication +{ + /// + /// Settings for authentication with an Azure or Azure Stack service using Active Directory. + /// + public sealed class ActiveDirectoryServiceSettings + { + private Uri _authenticationEndpoint; + + private static readonly ActiveDirectoryServiceSettings AzureSettings = new ActiveDirectoryServiceSettings + { + AuthenticationEndpoint= new Uri("https://login.windows.net/"), + TokenAudience = new Uri("https://management.core.windows.net/"), + ValidateAuthority = true + }; + + private static readonly ActiveDirectoryServiceSettings AzureChinaSettings = new ActiveDirectoryServiceSettings + { + AuthenticationEndpoint= new Uri("https://login.chinacloudapi.cn/"), + TokenAudience = new Uri("https://management.core.chinacloudapi.cn/"), + ValidateAuthority = true + }; + + /// + /// Gets the serviceSettings for authentication with Azure + /// + public static ActiveDirectoryServiceSettings Azure { get { return AzureSettings; } } + + /// + /// Gets the serviceSettings for authentication with Azure China + /// + public static ActiveDirectoryServiceSettings AzureChina { get { return AzureChinaSettings; } } + + /// + /// Gets or sets the ActiveDirectory Endpoint for the Azure Environment + /// + public Uri AuthenticationEndpoint + { + get { return _authenticationEndpoint; } + set { _authenticationEndpoint = EnsureTrailingSlash(value); } + } + + /// + /// Gets or sets the Token audience for an endpoint + /// + public Uri TokenAudience { get; set; } + + /// + /// Gets or sets a value that determines whether the authentication endpoint should be validated with Azure AD + /// + public bool ValidateAuthority { get; set; } + + private static Uri EnsureTrailingSlash(Uri authenticationEndpoint) + { + if (authenticationEndpoint == null) + { + throw new ArgumentNullException("authenticationEndpoint"); + } + + UriBuilder builder = new UriBuilder(authenticationEndpoint); + if (!string.IsNullOrEmpty(builder.Query)) + { + throw new ArgumentOutOfRangeException(nameof(authenticationEndpoint), + Resources.AuthenticationEndpointContainsQuery); + } + + var path = builder.Path; + if (string.IsNullOrWhiteSpace(path)) + { + path = "/"; + } + else if (!path.EndsWith("/", StringComparison.Ordinal)) + { + path = path + "/"; + } + + builder.Path = path; + return builder.Uri; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/ApplicationTokenProvider.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/ApplicationTokenProvider.cs new file mode 100644 index 0000000000000..1f18822252bce --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/ApplicationTokenProvider.cs @@ -0,0 +1,560 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Clients.ActiveDirectory; +using Microsoft.Rest; +using Microsoft.Rest.Azure.Authentication.Internal; +using Microsoft.Rest.ClientRuntime.Azure.Authentication.Properties; + +namespace Microsoft.Rest.Azure.Authentication +{ + /// + /// Provides tokens for Azure Active Directory applications. + /// + public class ApplicationTokenProvider : ITokenProvider + { + private AuthenticationContext _authenticationContext; + private string _tokenAudience; + private IApplicationAuthenticationProvider _authentications; + private string _clientId; + private DateTimeOffset _expiration; + private string _accessToken; + private string _accessTokenType; + private static readonly TimeSpan ExpirationThreshold = TimeSpan.FromMinutes(5); + + /// + /// Create an application token provider that can retrieve tokens for the given application from the given context, using the given audience + /// and credential. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The authentication context to use when retrieving tokens. + /// The token audience to use when retrieving tokens. + /// The client credential for this application. + /// The token details provided when authenticating with the client credentials. + public ApplicationTokenProvider(AuthenticationContext context, string tokenAudience, ClientCredential credential, AuthenticationResult authenticationResult) + { + if (credential == null) + { + throw new ArgumentNullException("credential"); + } + + if (authenticationResult == null) + { + throw new ArgumentNullException("authenticationResult"); + } + + Initialize(context, tokenAudience, credential.ClientId, new MemoryApplicationAuthenticationProvider(credential), authenticationResult, authenticationResult.ExpiresOn); + } + + /// + /// Create an application token provider that can retrieve tokens for the given application from the given context, using the given audience + /// and certificate. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The authentication context to use when retrieving tokens. + /// The token audience to use when retrieving tokens. + /// The certificate associated with Active Directory application. + /// The token details provided when authenticating with the client credentials. + public ApplicationTokenProvider(AuthenticationContext context, string tokenAudience, ClientAssertionCertificate certificate, AuthenticationResult authenticationResult) + { + if (certificate == null) + { + throw new ArgumentNullException("certificate"); + } + + if (authenticationResult == null) + { + throw new ArgumentNullException("authenticationResult"); + } + + Initialize(context, tokenAudience, certificate.ClientId, + new CertificateAuthenticationProvider((clientId) => Task.FromResult(certificate)), + authenticationResult, authenticationResult.ExpiresOn); + } + + /// + /// Create an application token provider that can retrieve tokens for the given application from the given context, using the given audience + /// and credential store. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The authentication context to use when retrieving tokens. + /// The token audience to use when retrieving tokens + /// The client Id for this active directory application + /// The source of authentication information for this application. + /// The authenticationResult of initial authentication with the application credentials. + public ApplicationTokenProvider(AuthenticationContext context, string tokenAudience, string clientId, + IApplicationAuthenticationProvider authenticationStore, AuthenticationResult authenticationResult) + { + if (authenticationResult == null) + { + throw new ArgumentNullException("authenticationResult"); + } + + Initialize(context, tokenAudience, clientId, authenticationStore, authenticationResult, authenticationResult.ExpiresOn); + } + + /// + /// Create an application token provider that can retrieve tokens for the given application from the given context, using the given audience + /// and credential store. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The authentication context to use when retrieving tokens. + /// The token audience to use when retrieving tokens + /// The client Id for this active directory application + /// The source of authentication information for this application. + /// The authenticationResult of initial authentication with the application credentials. + /// The date of expiration for the current access token. + public ApplicationTokenProvider(AuthenticationContext context, string tokenAudience, string clientId, + IApplicationAuthenticationProvider authenticationStore, AuthenticationResult authenticationResult, DateTimeOffset tokenExpiration) + { + Initialize(context, tokenAudience, clientId, authenticationStore, authenticationResult, tokenExpiration); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using client credentials. Uses the default token cache and the default + /// service settings (authority, token audience) for log in to azure resource manager during authentication. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// The secret for this active directory application. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, string clientId, string secret) + { + return await LoginSilentAsync(domain, clientId, secret, ActiveDirectoryServiceSettings.Azure, TokenCache.DefaultShared); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using certificate credentials. Uses the default token cache and the default + /// service settings (authority, token audience) for log in to azure resource manager during authentication. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// The certificate associated with Active Directory application. + /// The certificate password. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, string clientId, byte[] certificate, string password) + { + return await LoginSilentAsync(domain, clientId, certificate, password, ActiveDirectoryServiceSettings.Azure, TokenCache.DefaultShared); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using client credentials. Uses the default service settings + /// (authority, token audience) for log in to azure resource manager during authentication. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// The secret for this active directory application. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, string clientId, string secret, TokenCache cache) + { + return await LoginSilentAsync(domain, clientId, secret, ActiveDirectoryServiceSettings.Azure, cache); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using certificate credentials. Uses the default service settings + /// (authority, token audience) for log in to azure resource manager during authentication. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// The certificate associated with Active Directory application. + /// The certificate password. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, string clientId, byte[] certificate, string password, TokenCache cache) + { + return await LoginSilentAsync(domain, clientId, certificate, password, ActiveDirectoryServiceSettings.Azure, cache); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using client credentials. + /// Uses the default token cache during authentication. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// The secret for this active directory application. + /// The active directory service side settings, including authority and token audience. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, string clientId, string secret, + ActiveDirectoryServiceSettings settings) + { + return await LoginSilentAsync(domain, clientId, secret, settings, TokenCache.DefaultShared); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using certificate credentials. + /// Uses the default token cache during authentication. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// The certificate associated with Active Directory application. + /// The certificate password. + /// The active directory service side settings, including authority and token audience. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, string clientId, byte[] certificate, string password, + ActiveDirectoryServiceSettings settings) + { + return await LoginSilentAsync(domain, clientId, certificate, password, settings, TokenCache.DefaultShared); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using client credentials. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// The secret for this active directory application. + /// The active directory service side settings, including authority and token audience. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, string clientId, string secret, + ActiveDirectoryServiceSettings settings, TokenCache cache) + { + return await LoginSilentAsync(domain, new ClientCredential(clientId, secret), settings, cache); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using certificate credential. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// The certificate associated with Active Directory application. + /// The certificate password. + /// The active directory service side settings, including authority and token audience. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync( + string domain, string clientId, byte[] certificate, string password, + ActiveDirectoryServiceSettings settings, TokenCache cache) + { +#if PORTABLE + return await LoginSilentAsync(domain, new ClientAssertionCertificate(clientId, certificate, password), + settings, cache); +#else + return await LoginSilentAsync(domain, new ClientAssertionCertificate(clientId, + new System.Security.Cryptography.X509Certificates.X509Certificate2(certificate)), + settings, cache); +#endif + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using a client credential. Uses the default token cache and the default + /// service settings for azure resource manager (authority, token audience) during authentication. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The client credential (client id and secret) for this active directory application. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, ClientCredential credential) + { + return await LoginSilentAsync(domain, credential, ActiveDirectoryServiceSettings.Azure, TokenCache.DefaultShared); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using a certificate credential. Uses the default token cache and the default + /// service settings for azure resource manager (authority, token audience) during authentication. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The certificate associated with Active Directory application. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentWithCertificateAsync(string domain, ClientAssertionCertificate certificate) + { + return await LoginSilentAsync(domain, certificate, ActiveDirectoryServiceSettings.Azure, TokenCache.DefaultShared); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using a client credential. Uses the default service settings + /// for azure resource manager (authority, token audience) during authentication. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The client credential (client id and secret) for this active directory application. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, ClientCredential credential, + TokenCache cache) + { + return await LoginSilentAsync(domain, credential, ActiveDirectoryServiceSettings.Azure, cache); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using a certificate credential. Uses the default service settings + /// for azure resource manager (authority, token audience) during authentication. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The certificate associated with Active Directory application. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentWithCertificateAsync(string domain, ClientAssertionCertificate certificate, + TokenCache cache) + { + return await LoginSilentAsync(domain, certificate, ActiveDirectoryServiceSettings.Azure, cache); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using a client credential. Uses the default token cache for authentication. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The client credential (client id and secret) for this active directory application. + /// The active directory service side settings, including authority and token audience. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, ClientCredential credential, + ActiveDirectoryServiceSettings settings) + { + return await LoginSilentAsync(domain, credential, settings, TokenCache.DefaultShared); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using a certificate credential. Uses the default token cache for authentication. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The certificate associated with Active Directory application. + /// The active directory service side settings, including authority and token audience. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentWithCertificateAsync(string domain, ClientAssertionCertificate certificate, + ActiveDirectoryServiceSettings settings) + { + return await LoginSilentAsync(domain, certificate, settings, TokenCache.DefaultShared); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using a client credential. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The client credential (client id and secret) for this active directory application. + /// The active directory service side settings, including authority and token audience. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, ClientCredential credential, + ActiveDirectoryServiceSettings settings, TokenCache cache) + { + return await LoginSilentAsync(domain, credential.ClientId, new MemoryApplicationAuthenticationProvider(credential), + settings, cache); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application using a certificate credential. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The certificate associated with Active Directory application. + /// The active directory service side settings, including authority and token audience. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, ClientAssertionCertificate certificate, + ActiveDirectoryServiceSettings settings, TokenCache cache) + { + return await LoginSilentAsync(domain, certificate.ClientId, + new CertificateAuthenticationProvider((clientId) => Task.FromResult(certificate)), settings, cache); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application. Uses the default token cache and default + /// service settings (authority and token audience) for authenticating with azure resource manager. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// A source for the secure secret for this application. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, string clientId, + IApplicationAuthenticationProvider authenticationProvider) + { + return await LoginSilentAsync(domain, clientId, authenticationProvider, ActiveDirectoryServiceSettings.Azure, TokenCache.DefaultShared); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application. Uses the default service settings + /// (authority and token audience) for authenticating with azure resource manager. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// A source for the secure secret for this application. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, string clientId, + IApplicationAuthenticationProvider authenticationProvider, TokenCache cache) + { + return await LoginSilentAsync(domain, clientId, authenticationProvider, ActiveDirectoryServiceSettings.Azure, cache); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application. Uses the default shared token cache. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// A source for the secure secret for this application. + /// The active directory service side settings, including authority and token audience. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, string clientId, + IApplicationAuthenticationProvider authenticationProvider, ActiveDirectoryServiceSettings settings) + { + return await LoginSilentAsync(domain, clientId, authenticationProvider, settings, TokenCache.DefaultShared); + } + + /// + /// Creates ServiceClientCredentials for authenticating requests as an active directory application. + /// See Active Directory Quickstart for .Net + /// for detailed instructions on creating an Azure Active Directory application. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// A source for the secure secret for this application. + /// The active directory service side settings, including authority and token audience. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + public static async Task LoginSilentAsync(string domain, string clientId, + IApplicationAuthenticationProvider authenticationProvider, ActiveDirectoryServiceSettings settings, TokenCache cache) + { + var audience = settings.TokenAudience.ToString(); + var context = GetAuthenticationContext(domain, settings, cache); + var authResult = await authenticationProvider.AuthenticateAsync(clientId, audience, context); + return new TokenCredentials( + new ApplicationTokenProvider(context, audience, clientId,authenticationProvider, authResult), + authResult.TenantId, + authResult.UserInfo == null ? null : authResult.UserInfo.DisplayableId); + } + +#if DEBUG && !PORTABLE + /// + /// For testing purposes only: allows testing token expiration. + /// + /// The active directory domain or tenantId to authenticate with. + /// The active directory clientId for the application. + /// A source for the secure secret for this application. + /// The active directory service side settings, including authority and token audience. + /// The token cache to target during authentication. + /// The token expiration. + /// A ServiceClientCredentials object that can authenticate http requests as the given application. + internal static async Task LoginSilentAsync(string domain, string clientId, + IApplicationAuthenticationProvider authenticationProvider, ActiveDirectoryServiceSettings settings, TokenCache cache, DateTimeOffset expiration) + { + var audience = settings.TokenAudience.ToString(); + var context = GetAuthenticationContext(domain, settings, cache); + var authResult = await authenticationProvider.AuthenticateAsync(clientId, audience, context); + return new TokenCredentials( + new ApplicationTokenProvider(context, audience, clientId,authenticationProvider, authResult, expiration), + authResult.TenantId, + authResult.UserInfo == null ? null : authResult.UserInfo.DisplayableId); + } +#endif + /// + /// Gets an access token from the token cache or from AD authentication endpoint. + /// Attempts to refresh the access token if it has expired. + /// + public virtual async Task GetAuthenticationHeaderAsync(CancellationToken cancellationToken) + { + try + { + AuthenticationResult result; + if (AccessTokenExpired) + { + result = await this._authentications.AuthenticateAsync(this._clientId, this._tokenAudience, this._authenticationContext).ConfigureAwait(false); + this._accessToken = result.AccessToken; + this._accessTokenType = result.AccessTokenType; + this._expiration = result.ExpiresOn; + } + + return new AuthenticationHeaderValue(this._accessTokenType, this._accessToken); + } + catch (AdalException authenticationException) + { + throw new AuthenticationException(Resources.ErrorAcquiringToken, authenticationException); + } + } + + protected virtual bool AccessTokenExpired + { + get { return DateTime.UtcNow + ExpirationThreshold >= this._expiration; } + } + + private void Initialize(AuthenticationContext context, string tokenAudience, string clientId, + IApplicationAuthenticationProvider authenticationStore, AuthenticationResult authenticationResult, DateTimeOffset tokenExpiration) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + + if (string.IsNullOrWhiteSpace(tokenAudience)) + { + throw new ArgumentNullException("tokenAudience"); + } + + if (string.IsNullOrWhiteSpace(clientId)) + { + throw new ArgumentNullException("clientId"); + } + + if (authenticationStore == null) + { + throw new ArgumentNullException("authenticationStore"); + } + if (authenticationResult == null) + { + throw new ArgumentNullException("authenticationResult"); + } + + this._authentications = authenticationStore; + this._clientId = clientId; + this._authenticationContext = context; + this._accessToken = authenticationResult.AccessToken; + this._accessTokenType = authenticationResult.AccessTokenType; + this._tokenAudience = tokenAudience; + this._expiration = tokenExpiration; + } + + private static AuthenticationContext GetAuthenticationContext(string domain, ActiveDirectoryServiceSettings serviceSettings, TokenCache cache) + { + return (cache == null) + ? new AuthenticationContext(serviceSettings.AuthenticationEndpoint + domain, + serviceSettings.ValidateAuthority) + : new AuthenticationContext(serviceSettings.AuthenticationEndpoint + domain, + serviceSettings.ValidateAuthority, + cache); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/AuthenticationException.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/AuthenticationException.cs new file mode 100644 index 0000000000000..8d60edb2cd5f1 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/AuthenticationException.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Runtime.Serialization; +using Microsoft.IdentityModel.Clients.ActiveDirectory; +using Microsoft.Rest; +#if !PORTABLE +using System.Security.Permissions; +#endif + +namespace Microsoft.Rest.Azure.Authentication +{ + /// + /// Authentication exception for Microsoft Rest Client for Azure. + /// +#if !PORTABLE + [Serializable] +#endif + public class AuthenticationException : RestException + { + + /// + /// Initializes a new instance of the AuthenticationException class. + /// + public AuthenticationException() + : base() + { + } + + /// + /// Initializes a new instance of the AuthenticationException class. + /// + /// Exception message. + public AuthenticationException(string message) + : this(message, null) + { + } + + /// + /// Initializes a new instance of the AuthenticationException class. + /// + /// Exception message. + /// Inner exception. + public AuthenticationException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Wrap an exception thrown by the ADAL library. This prevents client dependencies on a particular version fo ADAL. + /// + /// The exception message + /// The inner AdalException with additional details + internal AuthenticationException(string message, AdalException innerException) : + base(string.Format(CultureInfo.CurrentCulture, message, innerException.Message), innerException) + { + } + +#if !PORTABLE + /// + /// Initializes a new instance of the AuthenticationException class. + /// + /// Serialization info. + /// Streaming context. + protected AuthenticationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Serializes content of the exception. + /// + /// Serialization info. + /// Streaming context. + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + if (info == null) + { + throw new ArgumentNullException("info"); + } + } +#endif + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/CertificateAuthenticationProvider.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/CertificateAuthenticationProvider.cs new file mode 100644 index 0000000000000..f619dc5e85ed5 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/CertificateAuthenticationProvider.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Clients.ActiveDirectory; + +namespace Microsoft.Rest.Azure.Authentication +{ + public class CertificateAuthenticationProvider : IApplicationAuthenticationProvider + { + private Func> _assertionProvider; + + public CertificateAuthenticationProvider(byte[] certificate, string password) + { +#if PORTABLE + this._assertionProvider = (s) => Task.FromResult(new ClientAssertionCertificate(s, certificate, password)); +#else + this._assertionProvider = (s) => Task.FromResult(new ClientAssertionCertificate(s, + new System.Security.Cryptography.X509Certificates.X509Certificate2(certificate))); +#endif + } + + /// + /// Create an application authenticator using a certificate provider + /// + /// + public CertificateAuthenticationProvider(Func> provider) + { + this._assertionProvider = provider; + } + + public async Task AuthenticateAsync(string clientId, string audience, IdentityModel.Clients.ActiveDirectory.AuthenticationContext context) + { + var certificate = await this._assertionProvider(clientId).ConfigureAwait(false); + return await context.AcquireTokenAsync(audience, certificate); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/GlobalSuppressions.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/GlobalSuppressions.cs new file mode 100644 index 0000000000000..498e6224da7db --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/GlobalSuppressions.cs @@ -0,0 +1,55 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. +// +// To add a suppression to this file, right-click the message in the +// Code Analysis results, point to "Suppress Message", and click +// "In Suppression File". +// You do not need to add suppressions to this file manually. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", + "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ebd", Scope = "member", + Target = "Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings.#EnableEbdMagicCookie")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,Microsoft.Rest.Azure.Authentication.IApplicationAuthenticationProvider)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,Microsoft.Rest.Azure.Authentication.IApplicationAuthenticationProvider,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,Microsoft.Rest.Azure.Authentication.IApplicationAuthenticationProvider,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,Microsoft.Rest.Azure.Authentication.IApplicationAuthenticationProvider,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings,System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings,System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings,System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings,System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings,System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings,System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginWithPromptAsync(System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings,Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginSilentAsync(System.String,System.String,System.String,System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginSilentAsync(System.String,System.String,System.String,System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginSilentAsync(System.String,System.String,System.String,System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.UserTokenProvider.#LoginSilentAsync(System.String,System.String,System.String,System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ebd", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ActiveDirectoryClientSettings.#EnableEbdMagicCookie")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,System.Security.Cryptography.X509Certificates.X509Certificate2,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,System.Security.Cryptography.X509Certificates.X509Certificate2,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,System.Security.Cryptography.X509Certificates.X509Certificate2,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentWithCertificateAsync(System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentWithCertificateAsync(System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentWithCertificateAsync(System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,System.Security.Cryptography.X509Certificates.X509Certificate2)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,System.Byte[],System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,System.Byte[],System.String,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,System.Byte[],System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Login", Scope = "member", Target = "Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.#LoginSilentAsync(System.String,System.String,System.Byte[],System.String,Microsoft.Rest.Azure.Authentication.ActiveDirectoryServiceSettings,Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache)")] diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/IApplicationAuthenticationProvider.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/IApplicationAuthenticationProvider.cs new file mode 100644 index 0000000000000..9ed45bb7fb32e --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/IApplicationAuthenticationProvider.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Clients.ActiveDirectory; + +namespace Microsoft.Rest.Azure.Authentication +{ + /// + /// Interface to platform-specific methods for securely storing client credentials + /// + public interface IApplicationAuthenticationProvider + { + /// + /// Retrieve ClientCredentials for an active directory application. + /// + /// The active directory client Id of the application. + /// The audience to target + /// The authentication context + /// authentication result which can be used for authentication with the given audience. + Task AuthenticateAsync(string clientId, string audience, AuthenticationContext context); + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/IUserCredentialProvider.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/IUserCredentialProvider.cs new file mode 100644 index 0000000000000..163647bf1dba6 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/IUserCredentialProvider.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.IdentityModel.Clients.ActiveDirectory; + +namespace Microsoft.Rest.Azure.Authentication +{ + /// + /// Interface to platform-specific methods for securely storing user credentials + /// + public interface IUserCredentialProvider + { + /// + /// Retrieve credentials for the given user account. + /// + /// The username for the account. + /// A UserCredential that can be used for AD authentication for the given account. + Task GetCredentialAsync(string username); + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/MemoryApplicationAuthenticationProvider.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/MemoryApplicationAuthenticationProvider.cs new file mode 100644 index 0000000000000..46dda716cc567 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/MemoryApplicationAuthenticationProvider.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Clients.ActiveDirectory; + +namespace Microsoft.Rest.Azure.Authentication.Internal +{ + /// + /// In memory store for application credentials. + /// + public class MemoryApplicationAuthenticationProvider : IApplicationAuthenticationProvider + { + private IDictionary _credentials; + + /// + /// Intializes an in-memory store of application credentials + /// + public MemoryApplicationAuthenticationProvider() + { + this._credentials = new Dictionary(); + } + + /// + /// Initializes an in-memory store of application credentials starting with the given credential + /// + /// + public MemoryApplicationAuthenticationProvider(ClientCredential credential) + : this() + { + AddCredential(credential); + } + + /// + /// Add the given credential to the in-memory store. + /// + /// The credential to add. + public void AddCredential(ClientCredential credential) + { + if (!_credentials.ContainsKey(credential.ClientId)) + { + _credentials[credential.ClientId] = credential; + } + } + + /// + /// Authenticate using the credentials stored for the given client id + /// + /// The Application ID for this service principal + /// The intended audicne for authentication + /// The AD AuthenticationContext to use + /// + public async Task AuthenticateAsync(string clientId, string audience, AuthenticationContext context) + { + if (_credentials.ContainsKey(clientId)) + { + var creds = _credentials[clientId]; + return await context.AcquireTokenAsync(audience, creds); + } + + throw new AuthenticationException("Matching credentials for client id '{0}' could not be found."); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Microsoft.Rest.ClientRuntime.Azure.Authentication.sln b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Microsoft.Rest.ClientRuntime.Azure.Authentication.sln new file mode 100644 index 0000000000000..bfa68309bda75 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Microsoft.Rest.ClientRuntime.Azure.Authentication.sln @@ -0,0 +1,52 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime", "..\Microsoft.Rest.ClientRuntime\Microsoft.Rest.ClientRuntime.xproj", "{EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Azure.Authentication", "Microsoft.Rest.ClientRuntime.Azure.Authentication.xproj", "{6319205D-BBFC-4150-BEAE-31B1C9B911DD}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Azure", "..\Microsoft.Rest.ClientRuntime.Azure\Microsoft.Rest.ClientRuntime.Azure.xproj", "{D5296EAB-C13E-4A88-9532-BD0677D18EC9}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Azure.Tests", "..\Microsoft.Rest.ClientRuntime.Azure.Tests\Microsoft.Rest.ClientRuntime.Azure.Tests.xproj", "{3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Tests", "..\Microsoft.Rest.ClientRuntime.Tests\Microsoft.Rest.ClientRuntime.Tests.xproj", "{F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Tracing.Tests", "..\Microsoft.Rest.ClientRuntime.Tracing.Tests\Microsoft.Rest.ClientRuntime.Tracing.Tests.xproj", "{52C61F15-BF86-41DC-93D1-05D3DA70F032}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Release|Any CPU.Build.0 = Release|Any CPU + {6319205D-BBFC-4150-BEAE-31B1C9B911DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6319205D-BBFC-4150-BEAE-31B1C9B911DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6319205D-BBFC-4150-BEAE-31B1C9B911DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6319205D-BBFC-4150-BEAE-31B1C9B911DD}.Release|Any CPU.Build.0 = Release|Any CPU + {D5296EAB-C13E-4A88-9532-BD0677D18EC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5296EAB-C13E-4A88-9532-BD0677D18EC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5296EAB-C13E-4A88-9532-BD0677D18EC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5296EAB-C13E-4A88-9532-BD0677D18EC9}.Release|Any CPU.Build.0 = Release|Any CPU + {3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}.Release|Any CPU.Build.0 = Release|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Release|Any CPU.Build.0 = Release|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Microsoft.Rest.ClientRuntime.Azure.Authentication.xproj b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Microsoft.Rest.ClientRuntime.Azure.Authentication.xproj new file mode 100644 index 0000000000000..cb62de18f7108 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Microsoft.Rest.ClientRuntime.Azure.Authentication.xproj @@ -0,0 +1,23 @@ + + + + ..\..\..\ + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 6319205d-bbfc-4150-beae-31b1c9b911dd + Microsoft.Rest.ClientRuntime.Azure.Authentication + .\obj + .\bin\ + + + + 2.0 + + + True + + + diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Properties/AssemblyInfo.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000..7457d74fd7352 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Properties/AssemblyInfo.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Microsoft Rest Azure Client Runtime Authentication")] +[assembly: AssemblyDescription("Client authentication infrastructure for Azure client libraries.")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.2.8.0")] + +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft Corporation")] +[assembly: AssemblyProduct("Azure .NET SDK")] +[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] +[assembly: CLSCompliant(false)] +[assembly: ComVisible(false)] diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Properties/Resources.Designer.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Properties/Resources.Designer.cs new file mode 100644 index 0000000000000..68e7e4a1ed286 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Properties/Resources.Designer.cs @@ -0,0 +1,89 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Rest.ClientRuntime.Azure.Authentication.Properties { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Rest.ClientRuntime.Azure.Authentication.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The authentication endpoint must not contain a query string.. + /// + internal static string AuthenticationEndpointContainsQuery { + get { + return ResourceManager.GetString("AuthenticationEndpointContainsQuery", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Authentication error while acquiring token: '{0}'.. + /// + internal static string ErrorAcquiringToken { + get { + return ResourceManager.GetString("ErrorAcquiringToken", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Authentication error while renewing token: '{0}'.. + /// + internal static string ErrorRenewingToken { + get { + return ResourceManager.GetString("ErrorRenewingToken", resourceCulture); + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Properties/Resources.resx b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Properties/Resources.resx new file mode 100644 index 0000000000000..bbcd44492dc3f --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Properties/Resources.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The authentication endpoint must not contain a query string. + + + Authentication error while acquiring token: '{0}'. + + + Authentication error while renewing token: '{0}'. + + \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Settings.SourceAnalysis b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Settings.SourceAnalysis new file mode 100644 index 0000000000000..2e1b08cea6461 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/Settings.SourceAnalysis @@ -0,0 +1,196 @@ + + + NoMerge + + dq + dq-lit + eq + esc-dq + esc-eq + esc-sq + sq + sq-lit + ws + + + + + + + + True + + + + + True + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + Microsoft + [###LICENSE_NAME###] + True + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + + as + do + id + if + in + is + my + no + on + to + ui + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/UserTokenProvider.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/UserTokenProvider.cs new file mode 100644 index 0000000000000..5a918f0d4e837 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/UserTokenProvider.cs @@ -0,0 +1,607 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Clients.ActiveDirectory; +using Microsoft.Rest.ClientRuntime.Azure.Authentication.Properties; + +namespace Microsoft.Rest.Azure.Authentication +{ + /// + /// Provides tokens for Azure Active Directory Microsoft Id and Organization Id users. + /// + public class UserTokenProvider : ITokenProvider + { + /// + /// The id of the active directory common tenant. + /// + public const string CommonTenantId = "common"; + /// + /// Uri parameters used in the credential prompt. Allows recalling previous + /// logins in the login dialog. + /// + private string _tokenAudience; + private AuthenticationContext _authenticationContext; + private string _clientId; + private UserIdentifier _userid; + + /// + /// Create a token provider which can provide user tokens in the given context. The user must have previously authenticated in the given context. + /// Tokens are retrieved from the token cache. + /// + /// The active directory authentication context to use for retrieving tokens. + /// The active directory client Id to match when retrieving tokens. + /// The audience to match when retrieving tokens. + /// The user id to match when retrieving tokens. + public UserTokenProvider(AuthenticationContext context, string clientId, Uri tokenAudience, + UserIdentifier userId) + { + if (context == null) + { + throw new ArgumentNullException("context"); + } + if (string.IsNullOrWhiteSpace(clientId)) + { + throw new ArgumentNullException("clientId"); + } + if (tokenAudience == null) + { + throw new ArgumentNullException("tokenAudience"); + } + if (userId == null) + { + throw new ArgumentNullException("userId"); + } + + this._authenticationContext = context; + this._clientId = clientId; + this._tokenAudience = tokenAudience.ToString(); + this._userid = userId; + } +// Interactive authentication is not implemented for CoreCLR. +#if !PORTABLE + /// + /// Log in to Azure active directory common tenant with user account and authentication provided by the user. Authentication is automatically scoped to the default azure management endpoint. + /// This call may display a credentials dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginWithPromptAsync( + ActiveDirectoryClientSettings clientSettings) + { + return await LoginWithPromptAsync(CommonTenantId, clientSettings, ActiveDirectoryServiceSettings.Azure, + UserIdentifier.AnyUser, TokenCache.DefaultShared); + } + + /// + /// Log in to Azure active directory common tenant using the given username, with authentication provided by the user. Authentication is automatically scoped to the default azure management endpoint. + /// This call may display a credentials dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// The username to use for authentication. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginWithPromptAsync( + ActiveDirectoryClientSettings clientSettings, string username, TokenCache cache) + { + return await LoginWithPromptAsync(CommonTenantId, clientSettings, ActiveDirectoryServiceSettings.Azure, + new UserIdentifier(username, UserIdentifierType.RequiredDisplayableId), cache); + } + + /// + /// Log in to Azure active directory common tenant using the given username, with authentication provided by the user. Authentication is automatically scoped to the default azure management endpoint. + /// This call may display a credentials dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// The username to use for authentication. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginWithPromptAsync( + ActiveDirectoryClientSettings clientSettings, string username) + { + return await LoginWithPromptAsync(CommonTenantId, clientSettings, ActiveDirectoryServiceSettings.Azure, + new UserIdentifier(username, UserIdentifierType.RequiredDisplayableId), TokenCache.DefaultShared); + } + + /// + /// Log in to Azure active directory common tenant with user account and authentication provided by the user. Authentication is automatically scoped to the default azure management endpoint. + /// This call may display a credentials dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// The token cache to target during authentication. + /// ServiceClientCredentials object for the common tenant that match provided authentication credentials. + public static async Task LoginWithPromptAsync( + ActiveDirectoryClientSettings clientSettings, TokenCache cache) + { + return await LoginWithPromptAsync(CommonTenantId, clientSettings, ActiveDirectoryServiceSettings.Azure, + UserIdentifier.AnyUser, cache); + } + + /// + /// Log in to Azure active directory with user account and authentication provided by the user. Authentication is automatically scoped to the default azure management endpoint. + /// This call may display a credentials dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The domain to authenticate against. + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginWithPromptAsync(string domain, + ActiveDirectoryClientSettings clientSettings) + { + return await LoginWithPromptAsync(domain, clientSettings, ActiveDirectoryServiceSettings.Azure, + UserIdentifier.AnyUser, TokenCache.DefaultShared); + } + + /// + /// Log in to Azure active directory using the given username with authentication provided by the user. Authentication is automatically scoped to the default azure management endpoint. + /// This call may display a credentials dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The domain to authenticate against. + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// The username to use for authentication. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginWithPromptAsync(string domain, + ActiveDirectoryClientSettings clientSettings, string username, TokenCache cache) + { + return await LoginWithPromptAsync(domain, clientSettings, ActiveDirectoryServiceSettings.Azure, + new UserIdentifier(username, UserIdentifierType.RequiredDisplayableId), cache); + } + + /// + /// Log in to Azure active directory using the given username with authentication provided by the user. Authentication is automatically scoped to the default azure management endpoint. + /// This call may display a credentials dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The domain to authenticate against. + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// The username to use for authentication. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginWithPromptAsync(string domain, + ActiveDirectoryClientSettings clientSettings, string username) + { + return await LoginWithPromptAsync(domain, clientSettings, ActiveDirectoryServiceSettings.Azure, + new UserIdentifier(username, UserIdentifierType.RequiredDisplayableId), TokenCache.DefaultShared); + } + + /// + /// Log in to Azure active directory with both user account and authentication credentials provided by the user. This call may display a + /// credentials dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The domain to authenticate against. + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginWithPromptAsync(string domain, + ActiveDirectoryClientSettings clientSettings, TokenCache cache) + { + return await LoginWithPromptAsync(domain, clientSettings, ActiveDirectoryServiceSettings.Azure, + UserIdentifier.AnyUser, cache); + } + + /// + /// Log in to Azure active directory with both user account and authentication credentials provided by the user. This call may display a + /// credentials dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The domain to authenticate against. + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// The settings for ad service, including endpoint and token audience + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginWithPromptAsync(string domain, + ActiveDirectoryClientSettings clientSettings, + ActiveDirectoryServiceSettings serviceSettings) + { + return await LoginWithPromptAsync(domain, clientSettings, serviceSettings, + UserIdentifier.AnyUser, TokenCache.DefaultShared); + } + + /// + /// Log in to Azure active directory with both user account and authentication credentials provided by the user. This call may display a + /// credentials dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The domain to authenticate against. + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// The settings for ad service, including endpoint and token audience + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginWithPromptAsync(string domain, + ActiveDirectoryClientSettings clientSettings, + ActiveDirectoryServiceSettings serviceSettings, TokenCache cache) + { + return await LoginWithPromptAsync(domain, clientSettings, serviceSettings, + UserIdentifier.AnyUser, cache); + } + + /// + /// Log in to Azure active directory using the given username with authentication provided by the user. This call may display a credentials + /// dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The domain to authenticate against. + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// The settings for ad service, including endpoint and token audience + /// The username to use for authentication. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginWithPromptAsync(string domain, + ActiveDirectoryClientSettings clientSettings, + ActiveDirectoryServiceSettings serviceSettings, string username) + { + return await LoginWithPromptAsync(domain, clientSettings, serviceSettings, + new UserIdentifier(username, UserIdentifierType.RequiredDisplayableId), TokenCache.DefaultShared); + } + + /// + /// Log in to Azure active directory using the given username with authentication provided by the user. This call may display a credentials + /// dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The domain to authenticate against. + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// The settings for ad service, including endpoint and token audience + /// The username to use for authentication. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginWithPromptAsync(string domain, + ActiveDirectoryClientSettings clientSettings, + ActiveDirectoryServiceSettings serviceSettings, string username, TokenCache cache) + { + return await LoginWithPromptAsync(domain, clientSettings, serviceSettings, + new UserIdentifier(username, UserIdentifierType.RequiredDisplayableId), cache); + } + + /// + /// Log in to Azure active directory with credentials provided by the user. This call may display a credentials + /// dialog, depending on the supplied client settings and the state of the token cache and user cookies. + /// + /// The domain to authenticate against. + /// The client settings to use for authentication. These determine when a dialog will be displayed. + /// The settings for ad service, including endpoint and token audience + /// The userid of the desired credentials + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginWithPromptAsync(string domain, ActiveDirectoryClientSettings clientSettings, + ActiveDirectoryServiceSettings serviceSettings, UserIdentifier userId, TokenCache cache) + { + var authenticationContext = GetAuthenticationContext(domain, serviceSettings, cache); + TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext(); + var task = new Task(() => + { + try + { + var result = authenticationContext.AcquireToken( + serviceSettings.TokenAudience.ToString(), + clientSettings.ClientId, + clientSettings.ClientRedirectUri, + clientSettings.PromptBehavior, + userId, + clientSettings.AdditionalQueryParameters); + return result; + } + catch (Exception e) + { + throw new AuthenticationException( + string.Format(CultureInfo.CurrentCulture, Resources.ErrorAcquiringToken, + e.Message), e); + } + }); + + task.Start(scheduler); + var authResult = await task.ConfigureAwait(false); + var newUserId = new UserIdentifier(authResult.UserInfo.DisplayableId, + UserIdentifierType.RequiredDisplayableId); + return new TokenCredentials( + new UserTokenProvider(authenticationContext, clientSettings.ClientId,serviceSettings.TokenAudience, newUserId), + authResult.TenantId, + authResult.UserInfo.DisplayableId); + } +#endif + /// + /// Log in to azure active directory in non-interactive mode using organizational id credentials and the default token cache. Default service + /// settings (authority, audience) for logging in to azure resource manager are used. + /// + /// The active directory client id for this application. + /// The active directory domain or tenant id to authenticate with. + /// The organizational account user name, given in the form of a user principal name (e.g. user1@contoso.org). + /// The organizational account password. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginSilentAsync(string clientId, string domain, string username, string password) + { + return await LoginSilentAsync(clientId, domain, username, password, ActiveDirectoryServiceSettings.Azure, TokenCache.DefaultShared); + } + + /// + /// Log in to azure active directory in non-interactive mode using organizational id credentials. Default service settings (authority, audience) + /// for logging in to azure resource manager are used. + /// + /// The active directory client id for this application. + /// The active directory domain or tenant id to authenticate with. + /// The organizational account user name, given in the form of a user principal name (e.g. user1@contoso.org). + /// The organizational account password. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginSilentAsync(string clientId, string domain, string username, + string password, TokenCache cache) + { + return await LoginSilentAsync(clientId, domain, username, password, ActiveDirectoryServiceSettings.Azure, cache); + } + + /// + /// Log in to azure active directory in non-interactive mode using organizational id credentials and the default token cache. + /// + /// The active directory client id for this application. + /// The active directory domain or tenant id to authenticate with. + /// The organizational account user name, given in the form of a user principal name (e.g. user1@contoso.org). + /// The organizational account password. + /// The active directory service details, including authentication endpoints and the intended token audience. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginSilentAsync(string clientId, string domain, string username, + string password, ActiveDirectoryServiceSettings serviceSettings) + { + return await LoginSilentAsync(clientId, domain, username, password, serviceSettings, TokenCache.DefaultShared); + } + + /// + /// Log in to azure active directory in non-interactive mode using organizational id credentials. + /// + /// The active directory client id for this application. + /// The active directory domain or tenant id to authenticate with. + /// The organizational account user name, given in the form of a user principal name (e.g. user1@contoso.org). + /// The organizational account password. + /// The active directory service details, including authentication endpoints and the intended token audience. + /// The token cache to target during authentication. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginSilentAsync(string clientId, string domain, string username, string password, + ActiveDirectoryServiceSettings serviceSettings, TokenCache cache) + { + var credentials = new UserCredential(username, password); + var authenticationContext = GetAuthenticationContext(domain, serviceSettings, cache); + try + { + var authResult = await authenticationContext.AcquireTokenAsync(serviceSettings.TokenAudience.ToString(), + clientId, credentials).ConfigureAwait(false); + return + new TokenCredentials( + new UserTokenProvider(authenticationContext, clientId,serviceSettings.TokenAudience, + new UserIdentifier(username, UserIdentifierType.RequiredDisplayableId)), + authResult.TenantId, + authResult.UserInfo == null ? null : authResult.UserInfo.DisplayableId); + } + catch (AdalException ex) + { + throw new AuthenticationException(Resources.ErrorAcquiringToken, ex); + } + catch(FormatException ex) + { + throw new AuthenticationException(Resources.ErrorAcquiringToken, ex); + } + } + + // please remove this preprocessor #if whenever ADAL will go public with the new library +#if PORTABLE + /// + /// Log in to azure active directory using device code authentication. + /// + /// The active directory client id for this application. + /// User provided callback to display device code request. if returns false no token will be acquired. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginByDeviceCodeAsync( + string clientId, + Func deviceCodeHandler) + { + return await LoginByDeviceCodeAsync(clientId, CommonTenantId, ActiveDirectoryServiceSettings.Azure, TokenCache.DefaultShared, deviceCodeHandler); + } + + /// + /// Log in to azure active directory using device code authentication. + /// + /// The active directory client id for this application. + /// The active directory domain or tenant id to authenticate with. + /// User provided callback to display device code request. if returns false no token will be acquired. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginByDeviceCodeAsync( + string clientId, + string domain, + Func deviceCodeHandler) + { + return await LoginByDeviceCodeAsync(clientId, domain, ActiveDirectoryServiceSettings.Azure, TokenCache.DefaultShared, deviceCodeHandler); + } + + /// + /// Log in to azure active directory using device code authentication. + /// + /// The active directory client id for this application. + /// The active directory domain or tenant id to authenticate with. + /// The token cache to target during authentication. + /// User provided callback to display device code request. if returns false no token will be acquired. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginByDeviceCodeAsync( + string clientId, + string domain, + TokenCache cache, + Func deviceCodeHandler) + { + return await LoginByDeviceCodeAsync(clientId, domain, ActiveDirectoryServiceSettings.Azure, cache, deviceCodeHandler); + } + + /// + /// Log in to azure active directory using device code authentication. + /// + /// The active directory client id for this application. + /// The active directory domain or tenant id to authenticate with. + /// The active directory service details, including authentication endpoints and the intended token audience. + /// User provided callback to display device code request. if returns false no token will be acquired. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginByDeviceCodeAsync( + string clientId, + string domain, + ActiveDirectoryServiceSettings serviceSettings, + Func deviceCodeHandler) + { + return await LoginByDeviceCodeAsync(clientId, domain, serviceSettings, TokenCache.DefaultShared, deviceCodeHandler); + } + + /// + /// Log in to azure active directory using device code authentication. + /// + /// The active directory client id for this application. + /// The active directory domain or tenant id to authenticate with. + /// The active directory service details, including authentication endpoints and the intended token audience. + /// The token cache to target during authentication. + /// User provided callback to display device code request. if returns false no token will be acquired. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the given credentials. + public static async Task LoginByDeviceCodeAsync( + string clientId, + string domain, + ActiveDirectoryServiceSettings serviceSettings, + TokenCache cache, + Func deviceCodeHandler) + { + if(deviceCodeHandler == null) + { + throw new ArgumentException("deviceCodeHandler"); + } + + var authenticationContext = GetAuthenticationContext(domain, serviceSettings, cache); + + try + { + DeviceCodeResult codeResult = await authenticationContext.AcquireDeviceCodeAsync( + serviceSettings.TokenAudience.ToString(), + clientId) + .ConfigureAwait(false); + + if (deviceCodeHandler(codeResult)) + { + AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenByDeviceCodeAsync(codeResult) + .ConfigureAwait(false); + + return new TokenCredentials( + new UserTokenProvider( + authenticationContext, + clientId, + serviceSettings.TokenAudience, + new UserIdentifier(authenticationResult.UserInfo.DisplayableId, UserIdentifierType.RequiredDisplayableId)), + authenticationResult.TenantId, + authenticationResult.UserInfo == null ? null : authenticationResult.UserInfo.DisplayableId); + } + + return null; + } + catch (AdalException ex) + { + throw new AuthenticationException(Resources.ErrorAcquiringToken, ex); + } + catch (FormatException ex) + { + throw new AuthenticationException(Resources.ErrorAcquiringToken, ex); + } + } +#endif + + /// + /// Create service client credentials using information cached from a previous login to azure resource manager using the default token cache. + /// Parameters are used to match existing tokens. + /// + /// The clientId to match when retrieving authentication tokens. + /// The active directory domain or tenant id to match when retrieving authentication tokens. + /// The account username to match when retrieving authentication tokens. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the retrieved credentials. If no + /// credentials can be retrieved, an authentication exception is thrown. + public static async Task CreateCredentialsFromCache(string clientId, string domain, + string username) + { + return await CreateCredentialsFromCache(clientId, domain, username, ActiveDirectoryServiceSettings.Azure, TokenCache.DefaultShared); + } + + /// + /// Create service client credentials using information cached from a previous login to azure resource manager. Parameters are used to match + /// existing tokens. + /// + /// The clientId to match when retrieving authentication tokens. + /// The active directory domain or tenant id to match when retrieving authentication tokens. + /// The account username to match when retrieving authentication tokens. + /// The token cache to target when retrieving tokens. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the retrieved credentials. If no + /// credentials can be retrieved, an authentication exception is thrown. + public static async Task CreateCredentialsFromCache(string clientId, string domain, + string username, TokenCache cache) + { + return await CreateCredentialsFromCache(clientId, domain, username, ActiveDirectoryServiceSettings.Azure, cache); + } + + /// + /// Create service client credentials using information cached from a previous login in the default token cache. Parameters are used to match + /// existing tokens. + /// + /// The clientId to match when retrieving authentication tokens. + /// The active directory domain or tenant id to match when retrieving authentication tokens. + /// The account username to match when retrieving authentication tokens. + /// The active directory service settings, including token authority and audience to match when retrieving tokens. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the retrieved credentials. If no + /// credentials can be retrieved, an authentication exception is thrown. + public static async Task CreateCredentialsFromCache(string clientId, string domain, + string username, ActiveDirectoryServiceSettings serviceSettings) + { + return await CreateCredentialsFromCache(clientId, domain, username, serviceSettings, TokenCache.DefaultShared); + } + + /// + /// Create service client credentials using information cached from a previous login. Parameters are used to match existing tokens. + /// + /// The clientId to match when retrieving authentication tokens. + /// The active directory domain or tenant id to match when retrieving authentication tokens. + /// The account username to match when retrieving authentication tokens. + /// The active directory service settings, including token authority and audience to match when retrieving tokens. + /// The token cache to target when retrieving tokens. + /// A ServiceClientCredentials object that can be used to authenticate http requests using the retrieved credentials. If no + /// credentials can be retrieved, an authentication exception is thrown. + public static async Task CreateCredentialsFromCache(string clientId, string domain, string username, + ActiveDirectoryServiceSettings serviceSettings, TokenCache cache) + { + var userId = new UserIdentifier(username, UserIdentifierType.RequiredDisplayableId); + var authenticationContext = GetAuthenticationContext(domain, serviceSettings, cache); + try + { + var authResult = await authenticationContext.AcquireTokenSilentAsync(serviceSettings.TokenAudience.ToString(), + clientId, userId).ConfigureAwait(false); + return + new TokenCredentials( + new UserTokenProvider(authenticationContext, clientId,serviceSettings.TokenAudience, userId), + authResult.TenantId, + authResult.UserInfo == null ? null : authResult.UserInfo.DisplayableId); + } + catch (AdalException ex) + { + throw new AuthenticationException(Resources.ErrorAcquiringToken, ex); + } + } + + /// + /// Gets an access token from the token cache or from AD authentication endpoint. Will attempt to + /// refresh the access token if it has expired. + /// + public virtual async Task GetAuthenticationHeaderAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + AuthenticationResult result = await this._authenticationContext.AcquireTokenSilentAsync(this._tokenAudience, + this._clientId, this._userid).ConfigureAwait(false); + return new AuthenticationHeaderValue(result.AccessTokenType, result.AccessToken); + } + catch (AdalException authenticationException) + { + throw new AuthenticationException(Resources.ErrorRenewingToken, authenticationException); + } + } + + private static AuthenticationContext GetAuthenticationContext(string domain, ActiveDirectoryServiceSettings serviceSettings, TokenCache cache) + { + var context = (cache == null + ? new AuthenticationContext(serviceSettings.AuthenticationEndpoint + domain, + serviceSettings.ValidateAuthority) + : new AuthenticationContext(serviceSettings.AuthenticationEndpoint + domain, + serviceSettings.ValidateAuthority, cache)); + return context; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/project.json b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/project.json new file mode 100644 index 0000000000000..53d0182f75e00 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Authentication/project.json @@ -0,0 +1,48 @@ +{ + "version": "2.2.8-preview", + "title": "Authentication for Azure Management Clients", + "description": "Provides ADAL based authentication for Azure management client libraries \nSupported Platforms:\n - Portable Class Libraries\n - .NET Framework 4.5\n - Windows 8\n - Windows Phone 8.1\n - DotNet Core", + "authors": [ "Microsoft" ], + "copyright": "Copyright (c) Microsoft Corporation", + + "packOptions": { + "summary": "Client infrastructure for Azure client library ADAL based authentication.", + "iconUrl": "http://go.microsoft.com/fwlink/?LinkID=288890", + "projectUrl": "https://github.com/Azure/AutoRest", + "licenseUrl": "https://raw.githubusercontent.com/Microsoft/dotnet/master/LICENSE", + "tags": [ "Microsoft AutoRest ClientRuntime REST adal" ], + "requireLicenseAcceptance": true, + }, + + "buildOptions": { + "delaySign": true, + "publicSign": false, + "keyFile": "../../../tools/MSSharedLibKey.snk" + }, + + "dependencies": { + "Microsoft.Rest.ClientRuntime": "[2.3.2,3.0)" + }, + + "frameworks": { + "net45": { + "dependencies": { + "Microsoft.IdentityModel.Clients.ActiveDirectory": "[2.28.1,3.0)" + } + }, + "netstandard1.1": { + "buildOptions": { "define": [ "PORTABLE" ] }, + "imports": ["dnxcore50", "portable-net45+win8"], + "dependencies": { + "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.6.212041202-alpha" + } + }, + "netstandard1.5": { + "buildOptions": { "define": [ "PORTABLE" ] }, + "imports": ["dnxcore50"], + "dependencies": { + "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.6.212041202-alpha" + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ActiveDirectoryCredentialsTest.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ActiveDirectoryCredentialsTest.cs new file mode 100644 index 0000000000000..affc4715af444 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ActiveDirectoryCredentialsTest.cs @@ -0,0 +1,286 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Reflection; +using System.Threading; +using Microsoft.IdentityModel.Clients.ActiveDirectory; +using Microsoft.Rest.Azure.Authentication; +using Microsoft.Rest.Azure.Authentication.Internal; +using Xunit; +using Xunit.Abstractions; +#if !PORTABLE +using System.Configuration; +#endif + +namespace Microsoft.Rest.ClientRuntime.Azure.Test +{ + [Collection("ADAL Test Collection")] + public class ActiveDirectoryCredentialsTest + { + private string _username; + private string _password; + private string _applicationId; + private string _secret; + private string _domain; + private string _certificatePassword; + + public ActiveDirectoryCredentialsTest() + { + IDictionary connectionProperties = + EnvironmentDependentFactAttribute.ParseConnectionString(Environment.GetEnvironmentVariable("ARM_Connection_String")); + + if (connectionProperties != null) + { + connectionProperties.TryGetValue("username", out this._username); + connectionProperties.TryGetValue("password", out this._password); + connectionProperties.TryGetValue("applicationid", out this._applicationId); + connectionProperties.TryGetValue("secret", out this._secret); + connectionProperties.TryGetValue("domain", out this._domain); + connectionProperties.TryGetValue("certificatePassword", out this._certificatePassword); + } + } + +#if !PORTABLE + [EnvironmentDependentFact] + public void CertificateTokenProviderRefreshWorks() + { + var thumbprint = "F064B7C7EACC942D10662A5115E047E94FA18498"; + System.Security.Cryptography.X509Certificates.X509Certificate2Collection certificates; + Assert.True(TryFindCertificatesInStore(thumbprint, System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine, out certificates)); + + var cache = new TestTokenCache(); + byte[] certificate = certificates[0].Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, _certificatePassword); + var credentials = ApplicationTokenProvider.LoginSilentAsync( + "1449d5b7-8a83-47db-ae4c-9b03e888bad0", + "20c58db7-4501-44e8-8e76-6febdb400c6b", + certificate, + _certificatePassword) + .GetAwaiter().GetResult(); + cache.ForceTokenExpiry(); + var client = new HttpClient(); + var request = new HttpRequestMessage(HttpMethod.Get, + new Uri("https://management.azure.com/subscriptions?api-version=2014-04-01-preview")); + credentials.ProcessHttpRequestAsync(request, CancellationToken.None).Wait(); + Assert.NotNull(request.Headers.Authorization); + var response = client.SendAsync(request).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [EnvironmentDependentFact] + public void UserCredentialsPopsDialog() + { + var cache = new TestTokenCache(); + var settings = ActiveDirectoryServiceSettings.Azure; + var credentials = UserTokenProvider.LoginWithPromptAsync(this._domain, + ActiveDirectoryClientSettings.UsePromptOnly("1950a258-227b-4e31-a9cf-717495945fc2", new Uri("urn:ietf:wg:oauth:2.0:oob")), + settings, this._username, cache).GetAwaiter().GetResult(); + var client = new HttpClient(); + + var request = new HttpRequestMessage(HttpMethod.Get, + new Uri("https://management.azure.com/subscriptions?api-version=2014-04-01-preview")); + credentials.ProcessHttpRequestAsync(request, CancellationToken.None).Wait(); + Assert.NotNull(request.Headers.Authorization); + var response = client.SendAsync(request).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + // Repeat with PromptBehavior.Never + credentials = UserTokenProvider.LoginWithPromptAsync(this._domain, + ActiveDirectoryClientSettings.UseCacheOrCookiesOnly("1950a258-227b-4e31-a9cf-717495945fc2",new Uri("urn:ietf:wg:oauth:2.0:oob")), + settings, this._username, cache).GetAwaiter().GetResult(); + request = new HttpRequestMessage(HttpMethod.Get, + new Uri("https://management.azure.com/subscriptions?api-version=2014-04-01-preview")); + credentials.ProcessHttpRequestAsync(request, CancellationToken.None).Wait(); + Assert.NotNull(request.Headers.Authorization); + response = client.SendAsync(request).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + // Repeat with getting tokens strictly from cache + credentials = UserTokenProvider.CreateCredentialsFromCache("1950a258-227b-4e31-a9cf-717495945fc2", this._domain, this._username, cache).GetAwaiter().GetResult(); + request = new HttpRequestMessage(HttpMethod.Get, + new Uri("https://management.azure.com/subscriptions?api-version=2014-04-01-preview")); + credentials.ProcessHttpRequestAsync(request, CancellationToken.None).Wait(); + Assert.NotNull(request.Headers.Authorization); + response = client.SendAsync(request).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } +#endif + + [EnvironmentDependentFact] + public void OrgIdCredentialWorksWithoutDialog() + { + var credentials = + UserTokenProvider.LoginSilentAsync("1950a258-227b-4e31-a9cf-717495945fc2", + this._domain, this._username, this._password).GetAwaiter().GetResult(); + var client = new HttpClient(); + var request = new HttpRequestMessage(HttpMethod.Get, + new Uri("https://management.azure.com/subscriptions?api-version=2014-04-01-preview")); + credentials.ProcessHttpRequestAsync(request, CancellationToken.None).Wait(); + Assert.NotNull(request.Headers.Authorization); + var response = client.SendAsync(request).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [EnvironmentDependentFact] + public void OrgIdCredentialsThrowsForInvalidCredentials() + { + var exception = Assert.Throws(() => UserTokenProvider.LoginSilentAsync("1950a258-227b-4e31-a9cf-717495945fc2", + this._domain, "unuseduser@thisdomain.com", "This is not a valid password").GetAwaiter().GetResult()); + Assert.NotNull(exception.InnerException); + Assert.Equal(typeof(AdalException), exception.InnerException.GetType()); + exception = Assert.Throws(() => UserTokenProvider.LoginSilentAsync("1950a258-227b-4e31-a9cf-717495945fc2", + this._domain, "bad_user@bad_domain.com", this._password).ConfigureAwait(false).GetAwaiter().GetResult()); + Assert.NotNull(exception.InnerException); + Assert.Equal(typeof(AdalException), exception.InnerException.GetType()); + exception = Assert.Throws(() => UserTokenProvider.LoginSilentAsync("1950a258-227b-4e31-a9cf-717495945fc2", "not-a-valid-domain", this._username, this._password).ConfigureAwait(false).GetAwaiter().GetResult()); + Assert.NotNull(exception.InnerException); + Assert.Equal(typeof(AdalServiceException), exception.InnerException.GetType()); + exception = Assert.Throws(() => UserTokenProvider.LoginSilentAsync("not-a-valid-client-id", this._domain, this._username, this._password) + .ConfigureAwait(false).GetAwaiter().GetResult()); + Assert.NotNull(exception.InnerException); + Assert.Equal(typeof(AdalServiceException), exception.InnerException.GetType()); + } + +#if !PORTABLE + [EnvironmentDependentFact] + public void CredentialsConstructorThrowsForInvalidValues() + { + TokenCache cache = new TestTokenCache(); + var settings = ActiveDirectoryServiceSettings.Azure; + Assert.ThrowsAsync(() => UserTokenProvider.LoginSilentAsync(null, + "microsoft.onmicrosoft.com", this._username, this._password, cache)); + Assert.ThrowsAsync(() => UserTokenProvider.LoginWithPromptAsync( + "microsoft.onmicrosoft.com", ActiveDirectoryClientSettings.UsePromptOnly(string.Empty, new Uri("urn:ietf:wg:oauth:2.0:oob")), + settings, cache)); + Assert.ThrowsAsync(() => UserTokenProvider.LoginWithPromptAsync(null, + ActiveDirectoryClientSettings.UsePromptOnly("1950a258-227b-4e31-a9cf-717495945fc2", new Uri("urn:ietf:wg:oauth:2.0:oob")), + settings, cache)); + Assert.ThrowsAsync(() => UserTokenProvider.LoginWithPromptAsync(string.Empty, + ActiveDirectoryClientSettings.UsePromptOnly("1950a258-227b-4e31-a9cf-717495945fc2", new Uri("urn:ietf:wg:oauth:2.0:oob")), + settings, cache)); + Assert.ThrowsAsync(() => UserTokenProvider.LoginSilentAsync("1950a258-227b-4e31-a9cf-717495945fc2", + "microsoft.onmicrosoft.com", null, this._password, cache)); + Assert.Throws(() => UserTokenProvider.LoginSilentAsync("1950a258-227b-4e31-a9cf-717495945fc2", + "microsoft.onmicrosoft.com", string.Empty, this._password, cache).ConfigureAwait(false).GetAwaiter().GetResult()); + Assert.ThrowsAsync(() =>UserTokenProvider.LoginSilentAsync("1950a258-227b-4e31-a9cf-717495945fc2", + "microsoft.onmicrosoft.com", this._username, null, cache)); + Assert.ThrowsAsync(() => UserTokenProvider.LoginSilentAsync("1950a258-227b-4e31-a9cf-717495945fc2", + "microsoft.onmicrosoft.com", this._username, string.Empty, cache)); + } +#endif + + [EnvironmentDependentFact] + public void UserTokenProviderRefreshWorks() + { + var cache = new TestTokenCache(); + var credentials = UserTokenProvider.LoginSilentAsync("1950a258-227b-4e31-a9cf-717495945fc2", this._domain, + this._username, this._password, cache).GetAwaiter().GetResult(); + cache.ForceTokenExpiry(); + var client = new HttpClient(); + var request = new HttpRequestMessage(HttpMethod.Get, + new Uri("https://management.azure.com/subscriptions?api-version=2014-04-01-preview")); + credentials.ProcessHttpRequestAsync(request, CancellationToken.None).Wait(); + Assert.NotNull(request.Headers.Authorization); + var response = client.SendAsync(request).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [EnvironmentDependentFact] + public void ValidApplicationCredentialsAuthenticateCorrectly() + { + var cache = new TestTokenCache(); + var credentials = ApplicationTokenProvider.LoginSilentAsync(this._domain, this._applicationId, this._secret, cache).GetAwaiter().GetResult(); + var client = new HttpClient(); + var request = new HttpRequestMessage(HttpMethod.Get, + new Uri("https://management.azure.com/subscriptions?api-version=2014-04-01-preview")); + credentials.ProcessHttpRequestAsync(request, CancellationToken.None).Wait(); + Assert.NotNull(request.Headers.Authorization); + var response = client.SendAsync(request).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + +#if !PORTABLE + [EnvironmentDependentFact] + public void ApplicationCredentialsCanBeRenewed() + { + var cache = new TestTokenCache(); + var credentials = ApplicationTokenProvider.LoginSilentAsync(this._domain, this._applicationId, + new MemoryApplicationAuthenticationProvider(new ClientCredential(this._applicationId, this._secret)), + ActiveDirectoryServiceSettings.Azure, cache).GetAwaiter().GetResult(); + var client = new HttpClient(); + var request = new HttpRequestMessage(HttpMethod.Get, + new Uri("https://management.azure.com/subscriptions?api-version=2014-04-01-preview")); + credentials.ProcessHttpRequestAsync(request, CancellationToken.None).Wait(); + Assert.NotNull(request.Headers.Authorization); + var response = client.SendAsync(request).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + private static bool TryFindCertificatesInStore(string thumbprint, + System.Security.Cryptography.X509Certificates.StoreLocation location, out System.Security.Cryptography.X509Certificates.X509Certificate2Collection certificates) + { + System.Security.Cryptography.X509Certificates.X509Store store = new System.Security.Cryptography.X509Certificates.X509Store(System.Security.Cryptography.X509Certificates.StoreName.My, location); + store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.ReadOnly); + certificates = store.Certificates.Find(System.Security.Cryptography.X509Certificates.X509FindType.FindByThumbprint, thumbprint, false); + store.Close(); + + + return certificates.Count > 0; + } +#endif + + class TestTokenCache : TokenCache + { + public void ForceTokenExpiry() + { + var expired = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(5); + var dictionaryProperty = typeof (TokenCache).GetField("tokenCacheDictionary", BindingFlags.NonPublic | BindingFlags.Instance); + IDictionary dictionary = dictionaryProperty.GetValue(this) as IDictionary; + foreach (var authValue in dictionary.Values) + { + var authResult = authValue.GetType().GetProperty("Result").GetValue(authValue) as AuthenticationResult; + var expiresOnProperty = typeof (AuthenticationResult).GetProperty("ExpiresOn"); + expiresOnProperty.SetValue(authResult, expired); + } + } + } + } + public class EnvironmentDependentFactAttribute : FactAttribute + { + public EnvironmentDependentFactAttribute(params string[] scenarios) + { + IDictionary connectionProperties = + ParseConnectionString(Environment.GetEnvironmentVariable("ARM_Connection_String")); + + if (connectionProperties == null || + (!(connectionProperties.ContainsKey("username") && + connectionProperties.ContainsKey("password") && + connectionProperties.ContainsKey("applicationid") && + connectionProperties.ContainsKey("secret") && + connectionProperties.ContainsKey("domain") && + connectionProperties.ContainsKey("certificatePassword")))) + { + Skip = "An environment variable: ARM_Connection_String=username=;password=;applicationid=;secret=;domain=;certificatePassword= is required to run this test"; + } + + } + + internal static IDictionary ParseConnectionString(string connectionString) + { + if (string.IsNullOrWhiteSpace(connectionString)) + { + return null; + } + Dictionary result = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var pairString in connectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + var pair = pairString.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries); + result[pair[0]] = pair[1]; + } + + return result; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ActiveDirectorySettingsTest.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ActiveDirectorySettingsTest.cs new file mode 100644 index 0000000000000..de56db8a05570 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ActiveDirectorySettingsTest.cs @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.IdentityModel.Clients.ActiveDirectory; +using Xunit; +using Microsoft.Rest.Azure.Authentication; + +namespace Microsoft.Rest.ClientRuntime.Azure.Test +{ + public class ActiveDirectorySettingsTest + { + + [Theory] + [InlineData("https://www.contoso.com")] + [InlineData("https://www.contoso.com/widgets")] + [InlineData("http://www.contoso.com/wdgets/moreWidgets")] + [InlineData("https://www.contoso.com:8080")] + [InlineData("https://www.contoso.com:8080/widgets")] + public void AzureEnvironmentAddsSlashToEndpoints(string inputUri) + { + var testEnvironment = new ActiveDirectoryServiceSettings + { + ValidateAuthority = true, + TokenAudience = new Uri("https://contoso.com/widgets/"), + AuthenticationEndpoint = new Uri(inputUri) + }; + Assert.Equal(inputUri + "/", testEnvironment.AuthenticationEndpoint.ToString()); + } + + [Theory] + [InlineData("https://www.contoso.com/")] + [InlineData("https://www.contoso.com/widgets/")] + [InlineData("http://www.contoso.com/wdgets/moreWidgets/")] + [InlineData("https://www.contoso.com:8080/")] + [InlineData("https://www.contoso.com:8080/widgets/")] + public void AzureEnvironmentDoesNotDuplicateSlash(string inputUri) + { + var testEnvironment = new ActiveDirectoryServiceSettings + { + ValidateAuthority = true, + TokenAudience = new Uri("https://contoso.com/widgets/"), + AuthenticationEndpoint = new Uri(inputUri) + }; + Assert.Equal(inputUri, testEnvironment.AuthenticationEndpoint.ToString()); + } + + [Theory] + [InlineData("https://www.contoso.com?widget=blue")] + [InlineData("https://www.contoso.com/widgets/?widget=blue&color=yellow")] + public void AzureEnvironmentRejectsInvalidUris(string inputUri) + { + Assert.Throws(() => new ActiveDirectoryServiceSettings + { + ValidateAuthority = true, + TokenAudience = new Uri("https://contoso.com/widgets/"), + AuthenticationEndpoint = new Uri(inputUri) + }); + } + + [Fact] + public void AzureEnvironmenThrowsOnQueryInUri() + { + var error = Assert.Throws(() => new ActiveDirectoryServiceSettings + { + ValidateAuthority = true, + TokenAudience = new Uri("https://contoso.com/widgets/"), + AuthenticationEndpoint = new Uri("https://contoso.com/widgets/?api=123"), + }); + Assert.True(error.Message.StartsWith("The authentication endpoint must not contain a query string.", StringComparison.CurrentCulture)); + } + + [Fact] + public void AzureEnvironmenThrowsOnNullUri() + { + Assert.Throws(() => new ActiveDirectoryServiceSettings + { + ValidateAuthority = true, + TokenAudience = new Uri("https://contoso.com/widgets/"), + AuthenticationEndpoint = null + }); + } + + [Fact] + public void PromptOnlyClientSettingsWillAlwaysPrompt() + { + var clientId = Guid.NewGuid().ToString(); + var clientUri = new Uri("https://www.contoso.com/callbacks/"); + var settings = ActiveDirectoryClientSettings.UsePromptOnly(clientId, clientUri); + Assert.Equal(clientId, settings.ClientId); + Assert.Equal(clientUri, settings.ClientRedirectUri); +#if !PORTABLE + Assert.Equal(PromptBehavior.Always, settings.PromptBehavior); +#endif + Assert.Equal(ActiveDirectoryClientSettings.EnableEbdMagicCookie, settings.AdditionalQueryParameters); + } + + [Fact] + public void NoPromptClientSettingsWillNeverPrompt() + { + var clientId = Guid.NewGuid().ToString(); + var clientUri = new Uri("https://www.contoso.com/callbacks/"); + var settings = ActiveDirectoryClientSettings.UseCacheOrCookiesOnly(clientId, clientUri); + Assert.Equal(clientId, settings.ClientId); + Assert.Equal(clientUri, settings.ClientRedirectUri); +#if !PORTABLE + Assert.Equal(PromptBehavior.Never, settings.PromptBehavior); +#endif + Assert.Equal(ActiveDirectoryClientSettings.EnableEbdMagicCookie, settings.AdditionalQueryParameters); + } + + [Fact] + public void AutoPromptClientSettingsWillPromptIfNecessary() + { + var clientId = Guid.NewGuid().ToString(); + var clientUri = new Uri("https://www.contoso.com/callbacks/"); + var settings = ActiveDirectoryClientSettings.UseCacheCookiesOrPrompt(clientId, clientUri); + Assert.Equal(clientId, settings.ClientId); + Assert.Equal(clientUri, settings.ClientRedirectUri); +#if !PORTABLE + Assert.Equal(PromptBehavior.Auto, settings.PromptBehavior); +#endif + Assert.Equal(ActiveDirectoryClientSettings.EnableEbdMagicCookie, settings.AdditionalQueryParameters); + } + + [Fact] + public void ClientSettingsDefaultToAutoPrompt() + { + var clientId = Guid.NewGuid().ToString(); + var clientUri = new Uri("https://www.contoso.com/callbacks/"); + var settings = new ActiveDirectoryClientSettings(clientId, clientUri); + Assert.Equal(clientId, settings.ClientId); + Assert.Equal(clientUri, settings.ClientRedirectUri); +#if !PORTABLE + Assert.Equal(PromptBehavior.Auto, settings.PromptBehavior); +#endif + Assert.Equal(ActiveDirectoryClientSettings.EnableEbdMagicCookie, settings.AdditionalQueryParameters); + } +} +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/CloudErrorJsonConverterTest.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/CloudErrorJsonConverterTest.cs new file mode 100644 index 0000000000000..663d30b82cb81 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/CloudErrorJsonConverterTest.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.Rest.Azure; +using Microsoft.Rest.Serialization; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.Rest.ClientRuntime.Azure.Test +{ + public class CloudErrorJsonConverterTest + { + [Fact] + public void TestCloudErrorDeserialization() + { + var expected = @" + { + ""error"": { + ""code"": ""BadArgument"", + ""message"": ""The provided database ‘foo’ has an invalid username."", + ""target"": ""query"", + ""details"": [ + { + ""code"": ""301"", + ""target"": ""$search"", + ""message"": ""$search query option not supported"" + } + ] + } + }"; + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new CloudErrorJsonConverter()); + var cloudError = JsonConvert.DeserializeObject(expected, deserializeSettings); + + Assert.Equal("The provided database ‘foo’ has an invalid username.", cloudError.Message); + Assert.Equal("BadArgument", cloudError.Code); + Assert.Equal("query", cloudError.Target); + Assert.Equal(1, cloudError.Details.Count); + Assert.Equal("301", cloudError.Details[0].Code); + } + + [Fact] + public void TestCloudErrorWithDifferentCasing() + { + var expected = "{\"Error\":{\"Code\":\"CatalogObjectNotFound\",\"Message\":\"datainsights.blah doesn't exist.\"}} "; + + var deserializationSettings = new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.IsoDateFormat, + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver(), + Converters = new List + { + new Iso8601TimeSpanConverter() + } + }; + deserializationSettings.Converters.Add(new ResourceJsonConverter()); + deserializationSettings.Converters.Add(new CloudErrorJsonConverter()); + + var cloudError = JsonConvert.DeserializeObject(expected, deserializationSettings); + + Assert.NotNull(cloudError); + Assert.Equal("datainsights.blah doesn't exist.", cloudError.Message); + Assert.Equal("CatalogObjectNotFound", cloudError.Code); + } + + + [Fact] + public void TestEmptyCloudErrorDeserialization() + { + var expected = "{}"; + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new CloudErrorJsonConverter()); + var cloudError = JsonConvert.DeserializeObject(expected, deserializeSettings); + + Assert.NotNull(cloudError); + Assert.Null(cloudError.Code); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/FakeServiceClient.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/FakeServiceClient.cs new file mode 100644 index 0000000000000..3f694e53c6801 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/FakeServiceClient.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Rest; + +namespace Microsoft.Rest.ClientRuntime.Azure.Test.Fakes +{ + public class FakeServiceClient : ServiceClient + { + private FakeServiceClient() : base() + { + // Prevent base constructor from executing + } + + public FakeServiceClient(HttpClientHandler httpMessageHandler) + : base(httpMessageHandler) + { + } + + public async Task DoStuff() + { + // Construct URL + string url = "http://www.microsoft.com"; + + // Create HTTP transport objects + HttpRequestMessage httpRequest = null; + + httpRequest = new HttpRequestMessage(); + httpRequest.Method = HttpMethod.Get; + httpRequest.RequestUri = new Uri(url); + + // Set Headers + httpRequest.Headers.Add("x-ms-version", "2013-11-01"); + + // Set Credentials + var cancellationToken = new CancellationToken(); + cancellationToken.ThrowIfCancellationRequested(); + return await this.HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); + } + + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/FakeServiceClientWithCredentials.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/FakeServiceClientWithCredentials.cs new file mode 100644 index 0000000000000..7ebc84047fc45 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/FakeServiceClientWithCredentials.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Rest; +using Microsoft.Azure; + +namespace Microsoft.Rest.ClientRuntime.Azure.Test.Fakes +{ + public class FakeServiceClientWithCredentials : ServiceClient + { + private Uri _baseUri; + private ServiceClientCredentials _credentials; + + /// + /// Initializes a new instance of the FakeServiceClientWithCredentials class. + /// + private FakeServiceClientWithCredentials() + : base() + { + } + + /// + /// Initializes a new instance of the FakeServiceClientWithCredentials class. + /// + public FakeServiceClientWithCredentials(ServiceClientCredentials credentials, Uri baseUri) + : base() + { + if (credentials == null) + { + throw new ArgumentNullException("credentials"); + } + if (baseUri == null) + { + throw new ArgumentNullException("baseUri"); + } + this._credentials = credentials; + this._baseUri = baseUri; + + this.Credentials.InitializeServiceClient(this); + } + + /// + /// Initializes a new instance of the FakeServiceClientWithCredentials class. + /// + public FakeServiceClientWithCredentials( ServiceClientCredentials credentials, Uri baseUri, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) + : base(rootHandler, handlers) + { + if (credentials == null) + { + throw new ArgumentNullException("credentials"); + } + if (baseUri == null) + { + throw new ArgumentNullException("baseUri"); + } + this._credentials = credentials; + this._baseUri = baseUri; + + this.Credentials.InitializeServiceClient(this); + } + + /// + /// Initializes a new instance of the FakeServiceClientWithCredentials class. + /// + public FakeServiceClientWithCredentials(ServiceClientCredentials credentials, Uri baseUri, params DelegatingHandler[] handlers) + : base(handlers) + { + if (credentials == null) + { + throw new ArgumentNullException("credentials"); + } + if (baseUri == null) + { + throw new ArgumentNullException("baseUri"); + } + this._credentials = credentials; + this._baseUri = baseUri; + + this.Credentials.InitializeServiceClient(this); + } + + /// + /// Initializes a new instance of the FakeServiceClientWithCredentials class. + /// + public FakeServiceClientWithCredentials(ServiceClientCredentials credentials, params DelegatingHandler[] handlers) + : base(handlers) + { + if (credentials == null) + { + throw new ArgumentNullException("credentials"); + } + this._credentials = credentials; + this._baseUri = new Uri("https://TBD"); + + this.Credentials.InitializeServiceClient(this); + } + + /// + /// Initializes a new instance of the FakeServiceClientWithCredentials class. + /// + public FakeServiceClientWithCredentials(ServiceClientCredentials credentials, HttpClientHandler rootHandler, params DelegatingHandler[] handlers) + : base(rootHandler, handlers) + { + if (credentials == null) + { + throw new ArgumentNullException("credentials"); + } + this._credentials = credentials; + this._baseUri = new Uri("https://TBD"); + + this.Credentials.InitializeServiceClient(this); + } + + public Uri BaseUri + { + get { return _baseUri; } + } + + public ServiceClientCredentials Credentials + { + get { return _credentials; } + } + + + public async Task DoStuff() + { + // Construct URL + string url = "http://www.microsoft.com"; + + // Create HTTP transport objects + HttpRequestMessage httpRequest = null; + + httpRequest = new HttpRequestMessage(); + httpRequest.Method = HttpMethod.Get; + httpRequest.RequestUri = new Uri(url); + + await this.Credentials.ProcessHttpRequestAsync(httpRequest, new CancellationToken()); + + // Set Headers + httpRequest.Headers.Add("x-ms-version", "2013-11-01"); + + // Set Credentials + var cancellationToken = new CancellationToken(); + cancellationToken.ThrowIfCancellationRequested(); + return await HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/PlaybackTestHandler.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/PlaybackTestHandler.cs new file mode 100644 index 0000000000000..0d93cedd7a3fc --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/PlaybackTestHandler.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Microsoft.Rest.ClientRuntime.Azure.Test.Fakes +{ + /// + /// Plays back specified HTTP messages + /// + public class PlaybackTestHandler : DelegatingHandler + { + private readonly List _responses; + + private int _counter; + + public List Requests { get; private set; } + + public PlaybackTestHandler() + { + _responses = new List(); + Requests = new List(); + _counter = 0; + } + + public PlaybackTestHandler(IEnumerable responses) : + this() + { + _responses.AddRange(responses); + } + + protected override async Task SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) + { + Requests.Add(request); + Debug.Assert(_counter < _responses.Count); + return await Task.Run(() => { return _responses[_counter++]; }); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/RecordedDelegatingHandler.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/RecordedDelegatingHandler.cs new file mode 100644 index 0000000000000..09aba17808e2f --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Fakes/RecordedDelegatingHandler.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; + +namespace Microsoft.Rest.ClientRuntime.Azure.Test.Fakes +{ + public class RecordedDelegatingHandler : DelegatingHandler + { + private readonly HttpResponseMessage _response; + + public RecordedDelegatingHandler() + { + StatusCodeToReturn = HttpStatusCode.Created; + } + + public RecordedDelegatingHandler(HttpResponseMessage response) + { + StatusCodeToReturn = HttpStatusCode.Created; + _response = response; + } + + public HttpContentHeaders ContentHeaders { get; private set; } + + public bool IsPassThrough { get; set; } + + public HttpMethod Method { get; private set; } + + public string Request { get; private set; } + + public HttpRequestHeaders RequestHeaders { get; private set; } + + public HttpStatusCode StatusCodeToReturn { get; set; } + + public Uri Uri { get; private set; } + + protected override async Task SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) + { + // Save request + if (request.Content == null) + { + Request = string.Empty; + } + else + { + Request = await request.Content.ReadAsStringAsync(); + } + RequestHeaders = request.Headers; + if (request.Content != null) + { + ContentHeaders = request.Content.Headers; + } + Method = request.Method; + Uri = request.RequestUri; + + // Prepare response + if (IsPassThrough) + { + return await base.SendAsync(request, cancellationToken); + } + else + { + if (_response != null) + { + return _response; + } + else + { + HttpResponseMessage response = new HttpResponseMessage(StatusCodeToReturn); + response.Content = new StringContent(""); + return response; + } + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/LongRunningOperationsTest.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/LongRunningOperationsTest.cs new file mode 100644 index 0000000000000..014446a6b9eb4 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/LongRunningOperationsTest.cs @@ -0,0 +1,1440 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using Microsoft.Rest.ClientRuntime.Azure.Test.Fakes; +using Microsoft.Azure.Management.Redis; +using Microsoft.Azure.Management.Redis.Models; +using Xunit; +using Microsoft.Azure; +using Microsoft.Rest.Azure; + +namespace Microsoft.Rest.ClientRuntime.Azure.Test +{ + public class LongRunningOperationsTest + { + [Fact] + public void TestCreateOrUpdateWithAsyncHeader() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockCreateOrUpdateWithTwoTries()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + + Assert.Equal(HttpMethod.Put, handler.Requests[0].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[0].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[1].Method); + Assert.Equal("http://custom/status", + handler.Requests[1].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[2].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[2].RequestUri.ToString()); + } + + [Fact] + public void TestCreateOrUpdateWithoutHeaderInResponses() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockCreateOrUpdateWithoutHeaderInResponses()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + + Assert.Equal(HttpMethod.Put, handler.Requests[0].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[0].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[1].Method); + Assert.Equal("http://custom/status", + handler.Requests[1].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[2].Method); + Assert.Equal("http://custom/status", + handler.Requests[2].RequestUri.ToString()); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[3].RequestUri.ToString()); + } + + [Fact] + public void TestAsyncOperationWithNoPayload() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockAsyncOperaionWithNoBody()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + var error = Assert.Throws(() => + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234")); + Assert.Equal("The response from long running operation does not contain a body.", error.Message); + } + + [Fact] + public void TestAsyncOperationWithEmptyPayload() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockAsyncOperaionWithEmptyBody()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + var error = Assert.Throws(() => + fakeClient.RedisOperations.Delete("rg", "redis", "1234")); + Assert.Equal("The response from long running operation does not contain a body.", error.Message); + } + + [Fact] + public void TestAsyncOperationWithBadPayload() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockAsyncOperaionWithBadPayload()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + var resource = fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.Equal("100", resource.Id); + } + + [Fact] + public void TestAsyncOperationWithMissingProvisioningState() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockAsyncOperaionWithMissingProvisioningState()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + var resource = fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.Equal("100", resource.Id); + } + + [Fact] + public void TestAsyncOperationWithNonSuccessStatusAndInvalidResponseContent() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockAsyncOperaionWithNonSuccessStatusAndInvalidResponseContent()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + var error = Assert.Throws(() => + fakeClient.RedisOperations.Delete("rg", "redis", "1234")); + Assert.Equal("Long running operation failed with status 'BadRequest'.", error.Message); + Assert.Null(error.Body); + } + + [Fact] + public void TestPutOperationWithoutProvisioningState() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockPutOperaionWithoutProvisioningStateInResponse()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + var resource = fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.Equal("100", resource.Id); + } + + [Fact] + public void TestPutOperationWithNonResource() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockPutOperaionWitNonResource()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + Sku sku = fakeClient.RedisOperations.CreateOrUpdateNonResource("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.Equal("foo", sku.Name); + Assert.Equal(3, handler.Requests.Count); + } + + [Fact] + public void TestPutOperationWithSubResource() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockPutOperaionWitSubResource()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + var resource = fakeClient.RedisOperations.CreateOrUpdateSubResource("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.Equal("100", resource.Id); + Assert.Equal(3, handler.Requests.Count); + } + + [Fact] + public void TestPutOperationWithImmediateSuccess() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockPutOperaionWithImmediateSuccess()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.Equal(1, handler.Requests.Count); + } + + [Fact] + public void TestDeleteOperationWithImmediateSuccessAndOkStatus() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockOperaionWithImmediateSuccessOKStatus()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.Delete("rg", "redis", "1234"); + Assert.Equal(1, handler.Requests.Count); + } + + [Fact] + public void TestDeleteOperationWithImmediateSuccessAndNoContentStatus() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockOperaionWithImmediateSuccessNoContentStatus()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.Delete("rg", "redis", "1234"); + Assert.Equal(1, handler.Requests.Count); + } + + [Fact] + public void TestPostOperationWithImmediateSuccessAndOkStatus() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockPostOperaionWithImmediateSuccessOKStatus()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + var sku = fakeClient.RedisOperations.Post("rg", "redis", "1234"); + Assert.Equal(1, handler.Requests.Count); + Assert.Equal("Family", sku.Family); + } + + [Fact] + public void TestPostOperationWithImmediateSuccessAndNoContentStatus() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockOperaionWithImmediateSuccessNoContentStatus()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.Post("rg", "redis", "1234"); + Assert.Equal(1, handler.Requests.Count); + } + + [Fact] + public void TestPostOperationWithBody() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockPostOperaionWithBody()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.Post("rg", "redis", "1234"); + Assert.Equal(2, handler.Requests.Count); + Assert.Equal(HttpMethod.Post, handler.Requests[0].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[0].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[1].Method); + Assert.Equal("http://custom/status", handler.Requests[1].RequestUri.ToString()); + } + + [Fact] + public void TestDeleteOperationWithNonRetryableErrorInResponse() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockDeleteOperaionWithNoRetryableErrorInResponse()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + var error = Assert.Throws(() => fakeClient.RedisOperations.Delete("rg", "redis", "1234")); + Assert.Equal("Long running operation failed with status 'BadRequest'.", error.Message); + Assert.Equal(2, handler.Requests.Count); + } + + /// + /// It's assumed that the same pattern is used throughout the long running operation and + /// the final http call returns status code OK for Azure-AsyncOperation. + /// + [Fact] + public void TestDeleteOperationWithoutHeaderInResponse() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockDeleteOperaionWithoutHeaderInResponse()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.Delete("rg", "redis", "1234"); + Assert.Equal(3, handler.Requests.Count); + Assert.Equal("http://custom/status", handler.Requests[1].RequestUri.ToString()); + Assert.Equal("http://custom/status", handler.Requests[2].RequestUri.ToString()); + } + + /// + /// It's assumed that the same pattern is used throughout the long running operation and + /// the final http call returns status code OK, Created or NoContent for Location. + /// + [Fact] + public void TestDeleteOperationWithoutLocationHeaderInResponse() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockDeleteOperaionWithoutLocationHeaderInResponse()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.Delete("rg", "redis", "1234"); + Assert.Equal(3, handler.Requests.Count); + Assert.Equal("http://custom/status", handler.Requests[1].RequestUri.ToString()); + Assert.Equal("http://custom/status", handler.Requests[2].RequestUri.ToString()); + } + + /// + /// It's assumed that the same pattern is used throughout the long running operation and + /// the final http request return an object with successfull state. + /// + [Fact] + public void TestCreateOrUpdateWithLocationHeaderWith202() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockCreateOrUpdateWithLocationHeaderAnd202()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + + Assert.Equal(4, handler.Requests.Count); + Assert.Equal(HttpMethod.Put, handler.Requests[0].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[0].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[1].Method); + Assert.Equal("http://custom/status", handler.Requests[1].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[2].Method); + Assert.Equal("http://custom/locationstatus", handler.Requests[2].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[3].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[3].RequestUri.ToString()); + } + + [Fact] + public void TestCreateOrUpdateWithAsyncHeaderWith202() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockCreateOrUpdateWithAsyncHeaderAnd202()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + + Assert.Equal(3, handler.Requests.Count); + Assert.Equal(HttpMethod.Put, handler.Requests[0].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[0].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[1].Method); + Assert.Equal("http://custom/status", handler.Requests[1].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[2].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", handler.Requests[2].RequestUri.ToString()); + } + + [Fact] + public void TestCreateOrUpdateWithWith202AndResource() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockCreateOrUpdateWithWith202AndResource()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + var resource = fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + + Assert.Equal(3, handler.Requests.Count); + Assert.Equal(HttpMethod.Put, handler.Requests[0].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[0].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[1].Method); + Assert.Equal("http://custom/status", handler.Requests[1].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[2].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[2].RequestUri.ToString()); + Assert.Equal("Succeeded", resource.ProvisioningState); + } + + [Fact] + public void TestPostWithResponse() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockPostWithResourceSku()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + var resource = fakeClient.RedisOperations.Post("rg", "redis", "1234"); + + Assert.Equal(2, handler.Requests.Count); + Assert.Equal(HttpMethod.Post, handler.Requests[0].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[0].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[1].Method); + Assert.Equal("http://custom/status", + handler.Requests[1].RequestUri.ToString()); + Assert.Equal("Family", resource.Family); + } + + [Fact] + public void TestCreateOrUpdateNoAsyncHeader() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockCreateOrUpdateWithNoAsyncHeader()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + + Assert.Equal(HttpMethod.Put, handler.Requests[0].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[0].RequestUri.ToString()); + + Assert.Equal(HttpMethod.Get, handler.Requests[1].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[1].RequestUri.ToString()); + + Assert.Equal(HttpMethod.Get, handler.Requests[2].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[2].RequestUri.ToString()); + } + + [Fact] + public void TestCreateOrUpdateFailedStatus() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockCreateOrUpdateWithFailedStatus()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + try + { + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.False(true, "Expected exception was not thrown."); + } + catch (CloudException ex) + { + Assert.Equal("Long running operation failed with status 'Failed'.", ex.Message); + Assert.Contains(AzureAsyncOperation.FailedStatus, ex.Response.Content); + } + + } + + [Fact] + public void TestCreateOrUpdateErrorHandling() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockCreateOrUpdateWithImmediateServerError()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + try + { + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.False(true, "Expected exception was not thrown."); + } + catch(CloudException ex) + { + Assert.Equal("The provided database ‘foo’ has an invalid username.", ex.Message); + } + } + + [Fact] + public void TestCreateOrUpdateNoErrorBody() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockCreateOrUpdateWithNoErrorBody()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + try + { + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + Assert.False(true, "Expected exception was not thrown."); + } + catch (CloudException ex) + { + Assert.Equal(HttpStatusCode.InternalServerError, ex.Response.StatusCode); + } + } + + [Fact] + public void TestCreateOrUpdateWithRetryAfter() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockCreateOrUpdateWithRetryAfterTwoTries()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + var now = DateTime.Now; + fakeClient.RedisOperations.CreateOrUpdate("rg", "redis", new RedisCreateOrUpdateParameters(), "1234"); + + Assert.True(DateTime.Now - now >= TimeSpan.FromSeconds(2)); + } + + [Fact] + public void TestDeleteWithAsyncHeader() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockDeleteWithAsyncHeaderTwoTries()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.Delete("rg", "redis", "1234"); + + Assert.Equal(HttpMethod.Delete, handler.Requests[0].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[0].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[1].Method); + Assert.Equal("http://custom/status", + handler.Requests[1].RequestUri.ToString()); + Assert.Equal(2, handler.Requests.Count); + } + + [Fact] + public void TestDeleteWithLocationHeader() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockDeleteWithLocationHeaderTwoTries()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + fakeClient.RedisOperations.Delete("rg", "redis", "1234"); + + Assert.Equal(HttpMethod.Delete, handler.Requests[0].Method); + Assert.Equal("https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis", + handler.Requests[0].RequestUri.ToString()); + Assert.Equal(HttpMethod.Get, handler.Requests[1].Method); + Assert.Equal("http://custom/location/status", + handler.Requests[1].RequestUri.ToString()); + Assert.Equal(2, handler.Requests.Count); + } + + [Fact] + public void TestDeleteWithLocationHeaderErrorHandling() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockDeleteWithLocationHeaderError()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + + try + { + fakeClient.RedisOperations.Delete("rg", "redis", "1234"); + Assert.False(true, "Expected exception was not thrown."); + } + catch (CloudException ex) + { + Assert.Null(ex.Body); + } + } + + [Fact] + public void TestDeleteWithLocationHeaderErrorHandlingSecondTime() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockDeleteWithLocationHeaderErrorInSecondCall()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + fakeClient.LongRunningOperationInitialTimeout = fakeClient.LongRunningOperationRetryTimeout = 0; + + var ex = Assert.Throws(()=>fakeClient.RedisOperations.Delete("rg", "redis", "1234")); + Assert.Equal("Long running operation failed with status 'InternalServerError'.", ex.Message); + } + + [Fact] + public void TestDeleteWithRetryAfter() + { + var tokenCredentials = new TokenCredentials("123", "abc"); + var handler = new PlaybackTestHandler(MockDeleteWithRetryAfterTwoTries()); + var fakeClient = new RedisManagementClient(tokenCredentials, handler); + var now = DateTime.Now; + fakeClient.RedisOperations.Delete("rg", "redis", "1234"); + + Assert.True(DateTime.Now - now >= TimeSpan.FromSeconds(2)); + } + + private IEnumerable MockCreateOrUpdateWithTwoTries() + { + var response1 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""InProgress"", + ""comment"": ""Resource defined structure"" + } + }") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""status"" : ""Succeeded"", + ""error"" : { + ""code"": ""BadArgument"", + ""message"": ""The provided database ‘foo’ has an invalid username."" + } + }") + }; + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""Succeeded"", + ""comment"": ""Resource defined structure"" + } + }") + }; + + yield return response3; + } + + private IEnumerable MockCreateOrUpdateWithoutHeaderInResponses() + { + var response1 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""InProgress"", + ""comment"": ""Resource defined structure"" + } + }") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""status"" : ""InProgress"", + ""error"" : { + ""code"": ""BadArgument"", + ""message"": ""The provided database ‘foo’ has an invalid username."" + } + }") + }; + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""status"" : ""Succeeded"", + ""error"" : { + ""code"": ""BadArgument"", + ""message"": ""The provided database ‘foo’ has an invalid username."" + } + }") + }; + + yield return response3; + + var response4 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""Succeeded"", + ""comment"": ""Resource defined structure"" + } + }") + }; + + yield return response4; + } + + private IEnumerable MockAsyncOperaionWithNoBody() + { + var response1 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""InProgress"", + ""comment"": ""Resource defined structure"" + } + }") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("") + }; + + yield return response2; + } + + private IEnumerable MockAsyncOperaionWithEmptyBody() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{}") + }; + + yield return response2; + } + + private IEnumerable MockAsyncOperaionWithBadPayload() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("") + }; + response1.Headers.Add("Location", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("null") + }; + response2.Headers.Add("Location", "http://custom/status2"); + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{ \"properties\": { \"provisioningState\" : \"Succeeded\" }, \"id\": \"100\", \"name\": \"foo\" }") + }; + + yield return response3; + } + + private IEnumerable MockAsyncOperaionWithMissingProvisioningState() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("") + }; + response1.Headers.Add("Location", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("null") + }; + response2.Headers.Add("Location", "http://custom/status2"); + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{ \"properties\": { }, \"id\": \"100\", \"name\": \"foo\" }") + }; + + yield return response3; + } + + private IEnumerable MockAsyncOperaionWithNonSuccessStatusAndInvalidResponseContent() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("") + }; + response1.Headers.Add("Location", "http://custom/status"); + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent("<") + }; + yield return response2; + } + + private IEnumerable MockPutOperaionWithoutProvisioningStateInResponse() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Created) + { + Content = new StringContent("{ \"properties\": { }, \"id\": \"100\", \"name\": \"foo\" }") + }; + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{ \"properties\": { }, \"id\": \"100\", \"name\": \"foo\" }") + }; + yield return response2; + } + + private IEnumerable MockPutOperaionWitNonResource() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("null") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent(@" + { + ""status"" : ""Succeeded"", + ""error"" : { + ""code"": ""BadArgument"", + ""message"": ""The provided database ‘foo’ has an invalid username."" + } + }") + }; + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""name"": ""foo"" + }") + }; + + yield return response3; + } + + private IEnumerable MockPutOperaionWitSubResource() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("{ \"properties\": { \"provisioningState\": \"InProgress\"}, \"id\": \"100\", \"name\": \"foo\" }") + }; + response1.Headers.Add("Location", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("") + }; + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""id"": ""100"" + }") + }; + + yield return response3; + } + + private IEnumerable MockPutOperaionWithImmediateSuccess() + { + var response1 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{ \"properties\": { \"provisioningState\": \"Succeeded\"}, \"id\": \"100\", \"name\": \"foo\" }") + }; + + yield return response1; + } + + private IEnumerable MockOperaionWithImmediateSuccessOKStatus() + { + var response1 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("") + }; + + yield return response1; + } + + private IEnumerable MockPostOperaionWithImmediateSuccessOKStatus() + { + var response1 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{\"Capacity\":1,\"Family\":\"Family\"}") + }; + + yield return response1; + } + + private IEnumerable MockOperaionWithImmediateSuccessNoContentStatus() + { + var response1 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("") + }; + + yield return response1; + } + + private IEnumerable MockPostOperaionWithBody() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("") + }; + response1.Headers.Add("Location", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("{ \"properties\": { \"provisioningState\": \"Succeeded\"}, \"id\": \"100\", \"name\": \"foo\" }") + }; + + yield return response2; + } + + private IEnumerable MockDeleteOperaionWithNoRetryableErrorInResponse() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("") + }; + response1.Headers.Add("Location", "http://custom/status"); + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.BadRequest) + { + Content = new StringContent("") + }; + yield return response2; + } + + private IEnumerable MockDeleteOperaionWithoutHeaderInResponse() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""status"" : ""InProgress"", + ""error"" : { + ""code"": ""BadArgument"", + ""message"": ""The provided database ‘foo’ has an invalid username."" + } + }") + }; + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""status"" : ""Succeeded"", + ""error"" : { + ""code"": ""BadArgument"", + ""message"": ""The provided database ‘foo’ has an invalid username."" + } + }") + }; + yield return response3; + } + + private IEnumerable MockDeleteOperaionWithoutLocationHeaderInResponse() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("") + }; + response1.Headers.Add("Location", "http://custom/status"); + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("") + }; + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.NoContent) + { + Content = new StringContent("") + }; + yield return response3; + } + + private IEnumerable MockCreateOrUpdateWithLocationHeaderAnd202() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("null") + }; + response1.Headers.Add("Location", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("") + }; + response2.Headers.Add("Location", "http://custom/locationstatus"); + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("") + }; + response3.Headers.Add("Location", "https://management.azure.com/subscriptions/1234/resourceGroups/rg/providers/Microsoft.Cache/Redis/redis"); + + yield return response3; + + var response4 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""Succeeded"", + ""comment"": ""Resource defined structure"" + } + }") + }; + + yield return response4; + } + + private IEnumerable MockCreateOrUpdateWithAsyncHeaderAnd202() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("null") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent(@" + { + ""status"" : ""Succeeded"", + ""error"" : { + ""code"": ""BadArgument"", + ""message"": ""The provided database ‘foo’ has an invalid username."" + } + }") + }; + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""InProgress"", + ""comment"": ""Resource defined structure"" + } + }") + }; + + yield return response3; + } + + private IEnumerable MockCreateOrUpdateWithWith202AndResource() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("null") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent(@" + { + ""status"" : ""Succeeded"", + ""error"" : { + ""code"": ""BadArgument"", + ""message"": ""The provided database ‘foo’ has an invalid username."" + } + }") + }; + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""Succeeded"", + ""comment"": ""Resource defined structure"" + } + }") + }; + + yield return response3; + } + + private IEnumerable MockPostWithResourceSku() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent("null") + }; + response1.Headers.Add("Location", "http://custom/status"); + + yield return response1; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""Capacity"": ""1"", + ""Family"": ""Family"" + }") + }; + + yield return response3; + } + + private IEnumerable MockCreateOrUpdateWithNoAsyncHeader() + { + var response1 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""InProgress"", + ""comment"": ""Resource defined structure"" + } + }") + }; + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""Anything other than Succeeded"", + ""comment"": ""Resource defined structure"" + } + }") + }; + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""Succeeded"", + ""comment"": ""Resource defined structure"" + } + }") + }; + + yield return response3; + } + + private IEnumerable MockCreateOrUpdateWithFailedStatus() + { + var response1 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""InProgress"", + ""comment"": ""Resource defined structure"" + } + }") + }; + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""Anything other than Succeeded"", + ""comment"": ""Resource defined structure"" + } + }") + }; + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""Failed"", + ""comment"": ""Resource defined structure"" + } + }") + }; + + yield return response3; + } + + private IEnumerable MockCreateOrUpdateWithImmediateServerError() + { + var response1 = new HttpResponseMessage(HttpStatusCode.InternalServerError) + { + Content = new StringContent(@" + { + ""error"": { + ""code"": ""BadArgument"", + ""message"": ""The provided database ‘foo’ has an invalid username."", + ""target"": ""query"", + ""details"": [ + { + ""code"": ""301"", + ""target"": ""$search"", + ""message"": ""$search query option not supported"" + } + ] + } + }") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + + yield return response1; + } + + private IEnumerable MockCreateOrUpdateWithNoErrorBody() + { + var response1 = new HttpResponseMessage(HttpStatusCode.InternalServerError) + { + Content = new StringContent(@"") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + + yield return response1; + } + + private IEnumerable MockCreateOrUpdateWithRetryAfterTwoTries() + { + var response1 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""InProgress"", + ""comment"": ""Resource defined structure"" + } + }") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + response1.Headers.Add("Retry-After", "2"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""status"" : ""Succeeded"", + ""error"" : { + ""code"": ""BadArgument"", + ""message"": ""The provided database ‘foo’ has an invalid username."" + } + }") + }; + + yield return response2; + + var response3 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""location"": ""North US"", + ""tags"": { + ""key1"": ""value 1"", + ""key2"": ""value 2"" + }, + + ""properties"": { + ""provisioningState"": ""Succeeded"", + ""comment"": ""Resource defined structure"" + } + }") + }; + + yield return response3; + } + + private IEnumerable MockDeleteWithAsyncHeaderTwoTries() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent(@"") + }; + response1.Headers.Add("Azure-AsyncOperation", "http://custom/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@" + { + ""status"" : ""Succeeded"", + ""error"" : { + ""code"": ""BadArgument"", + ""message"": ""The provided database ‘foo’ has an invalid username."" + } + }") + }; + + yield return response2; + } + + private IEnumerable MockDeleteWithLocationHeaderTwoTries() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent(@"") + }; + response1.Headers.Add("Location", "http://custom/location/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@"") + }; + + yield return response2; + } + + private IEnumerable MockDeleteWithLocationHeaderError() + { + var response1 = new HttpResponseMessage(HttpStatusCode.InternalServerError) + { + Content = new StringContent(@"") + }; + response1.Headers.Add("Location", "http://custom/location/status"); + + yield return response1; + } + + private IEnumerable MockDeleteWithLocationHeaderErrorInSecondCall() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent(@"") + }; + response1.Headers.Add("Location", "http://custom/location/status"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.InternalServerError) + { + Content = new StringContent(@"") + }; + + yield return response2; + } + + private IEnumerable MockDeleteWithRetryAfterTwoTries() + { + var response1 = new HttpResponseMessage(HttpStatusCode.Accepted) + { + Content = new StringContent(@"") + }; + response1.Headers.Add("Location", "http://custom/location/status"); + response1.Headers.Add("Retry-After", "3"); + + yield return response1; + + var response2 = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@"") + }; + + yield return response2; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Microsoft.Rest.ClientRuntime.Azure.Tests.xproj b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Microsoft.Rest.ClientRuntime.Azure.Tests.xproj new file mode 100644 index 0000000000000..b8b9e2ccc376d --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Microsoft.Rest.ClientRuntime.Azure.Tests.xproj @@ -0,0 +1,23 @@ + + + + ..\..\..\ + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 3b2346e5-5d1f-4b0a-aeee-f3afb9583a72 + Microsoft.Rest.ClientRuntime.Azure.Tests + .\obj + .\bin\ + + + 2.0 + + + + + + + diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ODataTests.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ODataTests.cs new file mode 100644 index 0000000000000..e267142696404 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ODataTests.cs @@ -0,0 +1,455 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.Rest.Azure; +using Microsoft.Rest.Azure.OData; +using Newtonsoft.Json; +using Xunit; +using System.Collections.Generic; + +namespace Microsoft.Rest.ClientRuntime.Azure.Test +{ + public class ODataTests + { + [Fact] + public void DefaultODataQueryTest() + { + var date = DateTime.SpecifyKind(new DateTime(2013, 11, 5), DateTimeKind.Utc); + var date2 = DateTime.SpecifyKind(new DateTime(2004, 11, 5), DateTimeKind.Utc); + + var result = FilterString.Generate(p => p.Foo == "foo" || p.Val < 20 || p.Foo == "bar" && p.Val == null && + p.Date > date2 && + p.Date < date && p.Values.Contains("x"), false); + string time1 = Uri.EscapeDataString("2004-11-05T00:00:00Z"); + string time2 = Uri.EscapeDataString("2013-11-05T00:00:00Z"); + string expected = string.Format("foo eq 'foo' or Val lt 20 or foo eq 'bar' and Val eq null and d gt '{0}' " + + "and d lt '{1}' and vals/any(c: c eq 'x')", time1, time2); + Assert.Equal(expected, result); + } + + [Fact] + public void ParenthesisAreIgnoredInExpression() + { + var result = FilterString.Generate(p => p.Foo == "bar" && (p.Foo == "foo" || p.Val < 20)); + Assert.Equal("foo eq 'bar' and foo eq 'foo' or Val lt 20", result); + } + + [Fact] + public void NullParametersAreIgnoredInTheBeginningOfExpression() + { + string foo = null; + var result = FilterString.Generate(p => p.Foo == foo || p.Val == null || p.Val == 10); + Assert.Equal("Val eq 10", result); + } + + [Fact] + public void NullParametersAreIgnoredInTheMiddleOfExpression() + { + var result = FilterString.Generate(p => p.Foo == "bar" || p.Val == null || p.Val == 10); + Assert.Equal("foo eq 'bar' or Val eq 10", result); + } + + [Fact] + public void NullParametersAreIgnoredInTheEndOfExpression() + { + var result = FilterString.Generate(p => p.Foo == "bar" && p.Val == 20 || p.Val == null); + Assert.Equal("foo eq 'bar' and Val eq 20", result); + } + + [Fact] + public void NullParametersAreNotIgnoredInExpression() + { + string foo = null; + var result = FilterString.Generate(p => p.Foo == foo || p.Val == null || p.Val == 10, false); + Assert.Equal("foo eq null or Val eq null or Val eq 10", result); + } + + [Fact] + public void NotEqualsIsSupported() + { + var result = FilterString.Generate(p => p.Val != 20); + Assert.Equal("Val ne 20", result); + } + + [Fact] + public void CompositePropertyIsSupported() + { + var result = FilterString.Generate(p => p.Param2.Bar == "test" && + p.Param2.Param3.Bar == "test2"); + Assert.Equal("param2/bar eq 'test' and param2/param3/bar eq 'test2'", result); + } + + [Fact] + public void NullExpressionReturnsExptyString() + { + var result = FilterString.Generate(null); + Assert.Equal("", result); + } + + [Fact] + public void ResourcePropertyIsSupported() + { + var result = FilterString.Generate(p => p.Size > 10); + Assert.Equal("properties/size gt 10", result); + } + + [Fact] + public void ConditionalOperatorNotSupported() + { + Assert.Throws( + () => FilterString.Generate(p => p.Val == (p.Boolean ? 20 : 30))); + } + + [Fact] + public void NotEqualsUnaryExpressionIsNotSupported() + { + Assert.Throws(() => FilterString.Generate(p => !p.Boolean)); + Assert.Throws(() => FilterString.Generate(p => !(p.Boolean))); + } + + [Fact] + public void ComplexUnaryOperatorsAreNotSupported() + { + Assert.Throws(() => FilterString.Generate(p => !(p.Boolean || p.Foo == "foo"))); + } + + [Fact] + public void BooleansAreSupported() + { + var result = FilterString.Generate(p => p.Boolean == true); + Assert.Equal("Boolean eq true", result); + result = FilterString.Generate(p => p.Boolean); + Assert.Equal("Boolean eq true", result); + result = FilterString.Generate(p => p.Boolean && p.Foo == "foo"); + Assert.Equal("Boolean eq true and foo eq 'foo'", result); + result = FilterString.Generate(p => p.Foo == "foo" && p.Boolean); + Assert.Equal("foo eq 'foo' and Boolean eq true", result); + } + + [Fact] + public void VerifyDeepPropertiesInODataFilter() + { + var param = new InputParam2 + { + Param = new InputParam1 + { + Value = "foo" + } + }; + var result = FilterString.Generate(p => p.Foo == param.Param.Value); + Assert.Equal("foo eq 'foo'", result); + } + + [Fact] + public void StartsWithWorksInODataFilter() + { + var param = new InputParam2 + { + Param = new InputParam1 + { + Value = "foo" + } + }; + var result = FilterString.Generate(p => p.Foo.StartsWith(param.Param.Value)); + Assert.Equal("startswith(foo,'foo')", result); + } + + [Fact] + public void StartsWithWorksWihNullInODataFilter() + { + var param = new InputParam2 + { + Param = new InputParam1 + { + Value = null + } + }; + var result = FilterString.Generate(p => p.Foo.StartsWith(param.Param.Value)); + Assert.Equal("", result); + } + + [Fact] + public void EndsWithWorksInODataFilter() + { + var result = FilterString.Generate(p => p.Foo.EndsWith("foo")); + Assert.Equal("endswith(foo, 'foo')", result); + } + + [Fact] + public void StringContainsWorksInODataFilter() + { + var param = new InputParam2 + { + Param = new InputParam1 + { + Value = "foo" + } + }; + var result = FilterString.Generate(p => p.Foo.Contains(param.Param.Value)); + Assert.Equal("foo/any(c: c eq 'foo')", result); + } + + [Fact] + public void ArrayContainsWorksInODataFilter() + { + var result = FilterString.Generate(p => p.Values.Contains("x")); + Assert.Equal("vals/any(c: c eq 'x')", result); + } + + [Fact] + public void DefaultDateTimeProducesProperStringInODataFilter() + { + var result = FilterString.Generate(p => p.Date2 == new DateTime(2012, 5, 1, 11, 5, 1, DateTimeKind.Utc)); + Assert.Equal("Date2 eq '" + Uri.EscapeDataString("2012-05-01T11:05:01Z") + "'", result); + } + + [Fact] + public void DateTimeIsConvertedToUtc() + { + var localDate = new DateTime(2012, 5, 1, 11, 5, 1, DateTimeKind.Local); + var utcDate = localDate.ToUniversalTime(); + var result = FilterString.Generate(p => p.Date2 == localDate); + Assert.Equal("Date2 eq '" + Uri.EscapeDataString(utcDate.ToString("yyyy-MM-ddTHH:mm:ssZ")) + "'", result); + } + + [Fact] + public void EncodingTheParameters() + { + var param = new InputParam1 + { + Value = "Microsoft.Web/sites" + }; + var result = FilterString.Generate(p => p.Foo == param.Value); + Assert.Equal("foo eq 'Microsoft.Web%2Fsites'", result); + } + + [Fact] + public void ODataQuerySupportsAllParameters() + { + var query = new ODataQuery(p => p.Foo == "bar") + { + Expand = "param1", + OrderBy = "d", + Skip = 10, + Top = 100 + }; + Assert.Equal("$filter=foo eq 'bar'&$orderby=d&$expand=param1&$top=100&$skip=10", query.ToString()); + } + + [Fact] + public void ODataQuerySupportsEmptyState() + { + var query = new ODataQuery(); + Assert.Equal("", query.ToString()); + query = new ODataQuery(p => p.Foo == null); + Assert.Equal("", query.ToString()); + var param = new InputParam1 + { + Value = null + }; + var paramEncoded = new InputParam1 + { + Value = "bar/car" + }; + query = new ODataQuery(p => p.Foo == param.Value); + Assert.Equal("", query.ToString()); + query = new ODataQuery(p => p.Foo == param.Value && p.AssignedTo(param.Value)); + Assert.Equal("", query.ToString()); + query = new ODataQuery(p => p.AssignedTo(param.Value)); + Assert.Equal("", query.ToString()); + query = new ODataQuery(p => p.AssignedTo(paramEncoded.Value)); + Assert.Equal("$filter=assignedTo('bar%2Fcar')", query.ToString()); + } + + [Fact] + public void ODataQuerySupportsCustomDateTimeOffsetFilter() + { + var param = new Param1 + { + SubmitTime = DateTimeOffset.Parse("2016-03-28T08:15:00.0971693+00:00"), + State = "Ended" + + }; + + var filter = new List(); + filter.Add(string.Format("submitTime lt datetimeoffset'{0}'", Uri.EscapeDataString(param.SubmitTime.Value.ToString("O")))); + filter.Add(string.Format("state ne '{0}'", param.State)); + var filterString = string.Join(" and ", filter.ToArray()); + + + var query = new ODataQuery + { + Filter = filterString + }; + Assert.Equal("$filter=submitTime lt datetimeoffset'2016-03-28T08%3A15%3A00.0971693%2B00%3A00' and state ne 'Ended'", query.ToString()); + } + + + [Fact] + public void ODataQuerySupportsPartialState() + { + var query = new ODataQuery(p => p.Foo == "bar") + { + Top = 100 + }; + Assert.Equal("$filter=foo eq 'bar'&$top=100", query.ToString()); + } + + [Fact] + public void ODataQuerySupportsPartialStateWithSlashes() + { + var queryString = "$filter=foo eq 'bar%2Fclub'&$top=100"; + var query = new ODataQuery(p => p.Foo == "bar/club") + { + Top = 100 + }; + Assert.Equal(queryString, query.ToString()); + } + + [Fact] + public void ODataQuerySupportsImplicitConversionFromFilterString() + { + ODataQuery query = "foo eq 'bar'"; + query.Top = 100; + Assert.Equal("$filter=foo eq 'bar'&$top=100", query.ToString()); + } + + [Fact] + public void ODataQuerySupportsImplicitConversionFromFullFilterString() + { + ODataQuery query = "$filter=foo eq 'bar'"; + query.Top = 100; + Assert.Equal("$filter=foo eq 'bar'&$top=100", query.ToString()); + } + + [Fact] + public void ODataQuerySupportsImplicitConversionFromQueryString() + { + ODataQuery query = "$filter=foo eq 'bar'&$top=100"; + Assert.Equal("$filter=foo eq 'bar'&$top=100", query.ToString()); + } + + [Fact] + public void ODataQuerySupportsTimeSpan() + { + var timeSpan = TimeSpan.FromMinutes(5); + var filterString = FilterString.Generate(parameters => parameters.TimeGrain == timeSpan); + + Assert.Equal(filterString, "timeGrain eq duration'PT5M'"); + } + + [Fact] + public void ODataQuerySupportsEnum() + { + var timeSpan = TimeSpan.FromMinutes(5); + var filterString = FilterString.Generate(parameters => parameters.EventChannels == EventChannels.Admin); + Console.WriteLine(filterString); + + Assert.Equal(filterString, "eventChannels eq 'Admin'"); + } + + [Fact] + public void ODataQuerySupportsMethod() + { + var param = new InputParam1 + { + Value = "Microsoft.Web/sites" + }; + var filterString = FilterString.Generate(parameters => parameters.AtScope() && + parameters.AssignedTo(param.Value)); + + Assert.Equal("atScope() and assignedTo('Microsoft.Web%2Fsites')", filterString); + } + } + + [Flags] + public enum EventChannels + { + Admin = 1, + Operation = 2, + Debug = 4, + Analytics = 8 + } + + public class Parameters + { + [JsonProperty("startTime")] + public DateTime? StartTime { get; set; } + + [JsonProperty("eventChannels")] + public EventChannels? EventChannels { get; set; } + + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("timeGrain")] + public TimeSpan? TimeGrain { get; set; } + + } + + public class Param1 + { + [JsonProperty("foo")] + public string Foo { get; set; } + public int? Val { get; set; } + public bool Boolean { get; set; } + [JsonProperty("d")] + public DateTime Date { get; set; } + public DateTime Date2 { get; set; } + [JsonProperty("submitTime")] + public DateTimeOffset? SubmitTime { get; set; } + [JsonProperty("state")] + public string State { get; set; } + [JsonProperty("vals")] + public string[] Values { get; set; } + [JsonProperty("param2")] + public Param2 Param2 { get; set; } + + [ODataMethodAttribute("assignedTo")] + public bool AssignedTo(string parameter) + { + return true; + } + [ODataMethodAttribute("atScope")] + public bool AtScope() + { + return true; + } + } + + public class Param2 + { + [JsonProperty("bar")] + public string Bar { get; set; } + + [JsonProperty("param3")] + public Param3 Param3 { get; set; } + } + + public class Param3 + { + [JsonProperty("bar")] + public string Bar { get; set; } + } + + public class AzureResource : IResource + { + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("properties.size")] + public int Size { get; set; } + } + + public class InputParam1 + { + public string Value { get; set; } + } + + public class InputParam2 + { + public InputParam1 Param { get; set; } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Properties/AssemblyInfo.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000..1b8f90662ce44 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using System.Resources; +using Xunit; + +[assembly: AssemblyTitle("Microsoft Rest Azure Client Runtime Tests")] +[assembly: AssemblyDescription("Tests for the Azure Client Runtime.")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.4.2.0")] + +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft Corporation")] +[assembly: AssemblyProduct("Azure .NET SDK Tests")] +[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] +[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ResourceJsonConverterTest.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ResourceJsonConverterTest.cs new file mode 100644 index 0000000000000..3832004bd7b65 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/ResourceJsonConverterTest.cs @@ -0,0 +1,502 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.Rest.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; +using Microsoft.Azure; +using Microsoft.Rest.Azure; +using Microsoft.Azure.Management.DataLake.Analytics.Models; + +namespace Microsoft.Rest.ClientRuntime.Azure.Test +{ + public class ResourceJsonConverterTest + { + [Fact] + public void TestResourceSerialization() + { + var sampleResource = new SampleResource() + { + Size = "3", + Child = new SampleResourceChild1() + { + ChildName1 = "name1" + }, + Location = "EastUS", + Plan = "testPlan", + }; + sampleResource.Tags = new Dictionary(); + sampleResource.Tags["tag1"] = "value1"; + var serializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + serializeSettings.Converters.Add(new ResourceJsonConverter()); + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + string json = JsonConvert.SerializeObject(sampleResource, serializeSettings); + Assert.Equal(@"{ + ""location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + }, + ""plan"": ""testPlan"", + ""properties"": { + ""size"": ""3"", + ""child"": { + ""dType"": ""SampleResourceChild1"", + ""properties"": { + ""name1"": ""name1"" + } + } + } +}", json); + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new ResourceJsonConverter()); + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + var deserializedResource = JsonConvert.DeserializeObject(json, deserializeSettings); + var jsonoverProcessed = JsonConvert.SerializeObject(deserializedResource, serializeSettings); + + Assert.Equal(json, jsonoverProcessed); + } + + [Fact] + public void TestResourceSerializationWithPolymorphism() + { + var sampleResource = new SampleResource() + { + Size = "3", + Child = new SampleResourceChild1(), + Location = "EastUS" + }; + sampleResource.Tags = new Dictionary(); + sampleResource.Tags["tag1"] = "value1"; + var serializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + serializeSettings.Converters.Add(new ResourceJsonConverter()); + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + string json = JsonConvert.SerializeObject(sampleResource, serializeSettings); + Assert.Equal(@"{ + ""dType"": ""SampleResource"", + ""location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + }, + ""properties"": { + ""size"": ""3"", + ""child"": { + ""dType"": ""SampleResourceChild1"" + } + } +}", json); + } + + [Fact] + public void TestGenericResourceSerialization() + { + var sampleResource = new GenericResource() + { + Location = "EastUS", + Properties = JObject.Parse("{ \"size\" : \"3\" }") + }; + sampleResource.Tags = new Dictionary(); + sampleResource.Tags["tag1"] = "value1"; + var serializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + serializeSettings.Converters.Add(new ResourceJsonConverter()); + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + string json = JsonConvert.SerializeObject(sampleResource, serializeSettings); + Assert.Equal(@"{ + ""location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + }, + ""properties"": { + ""size"": ""3"" + } +}", json); + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new ResourceJsonConverter()); + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + var deserializedResource = JsonConvert.DeserializeObject(json, deserializeSettings); + var jsonoverProcessed = JsonConvert.SerializeObject(deserializedResource, serializeSettings); + + Assert.Equal(json, jsonoverProcessed); + } + + [Fact] + public void TestGenericResourceWithNullPropertiesSerialization() + { + var sampleResource = new GenericResource() + { + Location = "EastUS" + }; + sampleResource.Tags = new Dictionary(); + sampleResource.Tags["tag1"] = "value1"; + var serializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + serializeSettings.Converters.Add(new ResourceJsonConverter()); + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + string json = JsonConvert.SerializeObject(sampleResource, serializeSettings); + Assert.Equal(@"{ + ""location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + } +}", json); + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new ResourceJsonConverter()); + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + var deserializedResource = JsonConvert.DeserializeObject(json, deserializeSettings); + var jsonoverProcessed = JsonConvert.SerializeObject(deserializedResource, serializeSettings); + + Assert.Equal(json, jsonoverProcessed); + } + + [Fact] + public void TestResourceWithNullPropertiesSerialization() + { + var sampleResource = new SampleResource() + { + Location = "EastUS" + }; + sampleResource.Tags = new Dictionary(); + sampleResource.Tags["tag1"] = "value1"; + var serializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + serializeSettings.Converters.Add(new ResourceJsonConverter()); + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + string json = JsonConvert.SerializeObject(sampleResource, serializeSettings); + Assert.Equal(@"{ + ""location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + } +}", json); + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new ResourceJsonConverter()); + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + var deserializedResource = JsonConvert.DeserializeObject(json, deserializeSettings); + var jsonoverProcessed = JsonConvert.SerializeObject(deserializedResource, serializeSettings); + + Assert.Equal(json, jsonoverProcessed); + } + + [Fact] + public void TestProvisioningStateDeserialization() + { + var expected = @"{ + ""location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + }, + ""properties"": { + ""size"": ""3"", + ""provisioningState"": ""some string"", + ""child"": { + ""dType"": ""SampleResourceChild1"", + ""name1"": ""name1"", + ""id"": ""child"" + } + } + }"; + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new ResourceJsonConverter()); + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + var deserializedResource = JsonConvert.DeserializeObject(expected, deserializeSettings); + + Assert.Equal("some string", deserializedResource.ProvisioningState); + } + + [Fact] + public void TestDeserializationOfResourceWithConflictingProperties() + { + var expected = @"{ + ""id"": ""123"", + ""location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + }, + ""properties"": { + ""size"": ""3"", + ""provisioningState"": ""some string"", + ""location"": ""Special Location"", + ""id"": ""Special Id"" + } + }"; + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new ResourceJsonConverter()); + var deserializedResource = JsonConvert.DeserializeObject(expected, deserializeSettings); + + Assert.Equal("Special Location", deserializedResource.SampleResourceWithConflictLocation); + Assert.Equal("Special Id", deserializedResource.SampleResourceWithConflictId); + Assert.Equal("123", deserializedResource.Id); + + var expectedSerializedJson = @"{ + ""location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + }, + ""properties"": { + ""location"": ""Special Location"", + ""id"": ""Special Id"" + } +}"; + var newJson = JsonConvert.SerializeObject(deserializedResource, deserializeSettings); + Assert.Equal(expectedSerializedJson, newJson); + } + + [Fact] + public void TestGenericResourceDeserialization() + { + var expected = @"{ + ""location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + }, + ""properties"": { + ""size"": ""3"", + ""provisioningState"": ""some string"", + ""child"": { + ""dType"": ""SampleResourceChild1"", + ""name1"": ""name1"", + ""id"": ""child"" + } + } + }"; + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new ResourceJsonConverter()); + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + var deserializedResource = JsonConvert.DeserializeObject(expected, deserializeSettings); + + Assert.Equal("some string", ((JObject)deserializedResource.Properties)["provisioningState"]); + Assert.Equal("EastUS", deserializedResource.Location); + Assert.Equal("value1", deserializedResource.Tags["tag1"]); + Assert.Equal("3", ((JObject)deserializedResource.Properties)["size"]); + Assert.Equal("some string", ((JObject)deserializedResource.Properties)["provisioningState"]); + Assert.Equal("name1", ((JObject)deserializedResource.Properties)["child"]["name1"]); + } + + [Fact] + public void Failure() + { + var expected = @"{ + ""id"": ""/subscriptions/1234564654654654654/resourceGroups/csmrg6766/providers/Microsoft.Web/sites/csmr6039"", + ""name"": ""csmr6039"", + ""type"": ""Microsoft.Web/sites"", + ""location"": ""South Central US"", + ""tags"": {}, + ""properties"": { + ""name"": ""csmr6039"", + ""state"": ""Running"", + ""hostNames"": [ + ""csmr6039.antares-int.windows-int.net"" + ], + ""webSpace"": ""csmrg6766-SouthCentralUSwebspace"", + ""selfLink"": ""https://azure.com/subscriptions/1234564654654654654/webspaces/csmrg6766-SouthCentralUSwebspace/sites/csmr6039"", + ""repositorySiteName"": ""csmr6039"", + ""owner"": null, + ""usageState"": 0, + ""enabled"": true, + ""adminEnabled"": true, + ""enabledHostNames"": [ + ""csmr6039.antares-int.windows-int.net"", + ""csmr6039.scm.antares-int.windows-int.net"" + ], + ""siteProperties"": { + ""metadata"": null, + ""properties"": [], + ""appSettings"": null + }, + ""availabilityState"": 0, + ""sslCertificates"": null, + ""csrs"": [], + ""cers"": null, + ""siteMode"": null, + ""hostNameSslStates"": [ + { + ""name"": ""csmr6039.antares-int.windows-int.net"", + ""sslState"": 0, + ""ipBasedSslResult"": null, + ""virtualIP"": null, + ""thumbprint"": null, + ""toUpdate"": null, + ""toUpdateIpBasedSsl"": null, + ""ipBasedSslState"": 0 + }, + { + ""name"": ""csmr6039.scm.antares-int.windows-int.net"", + ""sslState"": 0, + ""ipBasedSslResult"": null, + ""virtualIP"": null, + ""thumbprint"": null, + ""toUpdate"": null, + ""toUpdateIpBasedSsl"": null, + ""ipBasedSslState"": 0 + } + ], + ""computeMode"": null, + ""serverFarm"": ""Default1"", + ""webHostingPlan"": ""Default1"", + ""lastModifiedTimeUtc"": ""2014-06-24T22:04:45.16"", + ""storageRecoveryDefaultState"": ""Running"", + ""contentAvailabilityState"": 0, + ""runtimeAvailabilityState"": 0, + ""siteConfig"": null, + ""deploymentId"": ""csmr6039"", + ""trafficManagerHostNames"": null, + ""sku"": ""Free"" + } +}"; + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new ResourceJsonConverter()); + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + var deserializedResource = JsonConvert.DeserializeObject(expected, deserializeSettings); + + Assert.Equal("South Central US", deserializedResource.Location); + Assert.Equal("csmr6039", deserializedResource.Name); + } + + [Fact] + public void PolymorphismWithPage() + { + var expected = @"{ + ""jobId"": ""12ac4036-cb63-4244-a7dd-03a087062b43"", + ""name"": ""azure sdk data lake analytics job"", + ""type"": ""USql"", + ""submitter"": ""admin@aad264.ccsctp.net"", + ""account"": null, + ""degreeOfParallelism"": 2, + ""priority"": 0, + ""submitTime"": ""Sat, 12 Dec 2015 00:04:50 GMT"", + ""startTime"": null, + ""endTime"": null, + ""state"": ""Compiling"", + ""result"": ""Succeeded"", + ""errorMessage"": ""null"", + ""storageAccounts"": null, + ""stateAuditRecords"": [ + { + ""newState"": ""New"", + ""timeStamp"": ""Sat, 12 Dec 2015 00:04:50 GMT"", + ""requestedByUser"": null, + ""details"": ""userName:admin@aad264.ccsctp.net;submitMachine:N/A"" + } + ], + ""properties"": { + ""owner"": ""admin@aad264.ccsctp.net"", + ""runtimeVersion"": ""default"", + ""rootProcessNodeId"": ""00000000-0000-0000-0000-000000000000"", + ""algebraFilePath"": ""adl://azure.net/system/jobservice/jobs/Usql/2015/12/12/00/04/12ac4036-cb63-4244-a7dd-03a087062b43/algebra.xml"", + ""compileMode"": ""Semantic"", + ""errorSource"": ""Unknown"", + ""totalCompilationTime"": ""00:00:00"", + ""totalPausedTime"": ""00:00:00"", + ""totalQueuedTime"": ""00:00:00"", + ""totalRunningTime"": ""00:00:00"", + ""type"": ""USql"" + } +}"; + + var deserializationSettings = new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.IsoDateFormat, + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver(), + Converters = new List + { + new Iso8601TimeSpanConverter() + } + }; + deserializationSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("type")); + deserializationSettings.Converters.Add(new ResourceJsonConverter()); + deserializationSettings.Converters.Add(new CloudErrorJsonConverter()); + var deserializedResource = JsonConvert.DeserializeObject(expected, deserializationSettings); + + Assert.True(deserializedResource.Properties is USql); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/GenericResource.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/GenericResource.cs new file mode 100644 index 0000000000000..9f5dc50e06154 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/GenericResource.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Microsoft.Rest.Azure +{ + /// + /// Information for resource. + /// + public partial class GenericResource : IResource + { + /// + /// Gets the ID of the resource. + /// + [JsonProperty("id")] + public string Id { get; private set; } + + /// + /// Gets the name of the resource. + /// + [JsonProperty("name")] + public string Name { get; private set; } + + /// + /// Gets the type of the resource. + /// + [JsonProperty("type")] + public string Type { get; private set; } + + /// + /// Required. Gets or sets the location of the resource. + /// + [JsonProperty("location")] + public string Location { get; set; } + + /// + /// Optional. Gets or sets the tags attached to the resource. + /// + [JsonProperty("tags")] + public IDictionary Tags { get; set; } + + /// + /// Optional. Gets or sets the resource properties. + /// + [JsonProperty("properties")] + public object Properties { get; set; } + + /// + /// Validate the object. Throws ArgumentException or ArgumentNullException if validation fails. + /// + public virtual void Validate() + { + if (Location == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "Location"); + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/JobInformation.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/JobInformation.cs new file mode 100644 index 0000000000000..f6fa4660bf4d5 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/JobInformation.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator 0.13.0.0 +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Microsoft.Azure.Management.DataLake.Analytics.Models +{ + using System; + using System.Linq; + using System.Collections.Generic; + using Newtonsoft.Json; + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + using Microsoft.Rest.Azure; + + /// + /// The common Data Lake Analytics job information properties. + /// + public partial class JobInformation + { + /// + /// Initializes a new instance of the JobInformation class. + /// + public JobInformation() { } + + /// + /// Gets or sets the job's unique identifier. + /// + [JsonProperty(PropertyName = "jobId")] + public string JobId { get; set; } + + /// + /// Gets or sets the friendly name of the job. + /// + [JsonProperty(PropertyName = "name")] + public string Name { get; set; } + + /// + /// Gets or sets the user or account that submitted the job. + /// + [JsonProperty(PropertyName = "submitter")] + public string Submitter { get; set; } + + /// + /// Gets or sets the error message details for the job, if it failed. + /// + [JsonProperty(PropertyName = "errorMessage")] + public string ErrorMessage { get; set; } + + /// + /// Gets or sets the degree of parallelism used for this job. This + /// must have a minimum value of 2 + /// + [JsonProperty(PropertyName = "degreeOfParallelism")] + public int? DegreeOfParallelism { get; set; } + + /// + /// Gets or sets the priority value for the current job which must be + /// greater than 1. + /// + [JsonProperty(PropertyName = "priority")] + public int? Priority { get; set; } + + /// + /// Gets or sets the time the job was submitted to the service. + /// + [JsonProperty(PropertyName = "submitTime")] + public DateTime? SubmitTime { get; set; } + + /// + /// Gets or sets the start time of the job. + /// + [JsonProperty(PropertyName = "startTime")] + public DateTime? StartTime { get; set; } + + /// + /// Gets or sets the completion time of the job + /// + [JsonProperty(PropertyName = "endTime")] + public DateTime? EndTime { get; set; } + + /// + /// Gets or sets the job specific properties. + /// + [JsonProperty(PropertyName = "properties")] + public JobProperties Properties { get; set; } + + /// + /// Validate the object. Throws ArgumentException or ArgumentNullException if validation fails. + /// + public virtual void Validate() + { + if (JobId == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "JobId"); + } + if (Name == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "Name"); + } + if (Properties == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "Properties"); + } + if (this.Properties != null) + { + this.Properties.Validate(); + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/JobProperties.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/JobProperties.cs new file mode 100644 index 0000000000000..c4606671a230a --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/JobProperties.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator 0.13.0.0 +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Microsoft.Azure.Management.DataLake.Analytics.Models +{ + using System; + using System.Linq; + using System.Collections.Generic; + using Newtonsoft.Json; + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + using Microsoft.Rest.Azure; + + /// + /// The common Data Lake Analytics job properties. + /// + public partial class JobProperties + { + /// + /// Initializes a new instance of the JobProperties class. + /// + public JobProperties() { } + + /// + /// Initializes a new instance of the JobProperties class. + /// + public JobProperties(string script, string runtimeVersion = default(string)) + { + RuntimeVersion = runtimeVersion; + Script = script; + } + + /// + /// Gets or sets the runtime version of the U-SQL engine to use + /// + [JsonProperty(PropertyName = "runtimeVersion")] + public string RuntimeVersion { get; set; } + + /// + /// Gets or sets the U-SQL script to run + /// + [JsonProperty(PropertyName = "script")] + public string Script { get; set; } + + /// + /// Validate the object. Throws ArgumentException or ArgumentNullException if validation fails. + /// + public virtual void Validate() + { + if (Script == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "Script"); + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/Page.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/Page.cs new file mode 100644 index 0000000000000..9256dbb483bef --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/Page.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator 0.13.0.0 +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Microsoft.Azure.Management.DataLake.Analytics.Models +{ + using System.Collections.Generic; + using System.Linq; + using Newtonsoft.Json; + using Microsoft.Rest.Azure; + + /// + /// Defines a page in Azure responses. + /// + /// Type of the page content items + [JsonObject] + public class Page : IPage + { + /// + /// Gets the link to the next page. + /// + [JsonProperty("nextLink")] + public string NextPageLink { get; private set; } + + [JsonProperty("value")] + private IList Items{ get; set; } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// A an enumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + return (Items == null) ? Enumerable.Empty().GetEnumerator() : Items.GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// A an enumerator that can be used to iterate through the collection. + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/RedisManagementClient.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/RedisManagementClient.cs new file mode 100644 index 0000000000000..17e4b94ad4386 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/RedisManagementClient.cs @@ -0,0 +1,2317 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Management.Redis.Models; +using Microsoft.Rest; +using Microsoft.Rest.Serialization; +using Newtonsoft.Json; +using Microsoft.Rest.Azure; + +namespace Microsoft.Azure.Management.Redis.Models +{ + public partial class RedisCreateOrUpdateParameters + { + private string _location; + + /// + /// Optional. + /// + public string Location + { + get { return this._location; } + set { this._location = value; } + } + + private RedisProperties _properties; + + /// + /// Optional. + /// + public RedisProperties Properties + { + get { return this._properties; } + set { this._properties = value; } + } + } + + public partial class RedisResource : IResource + { + /// + /// Gets the ID of the resource. + /// + [JsonProperty("id")] + public string Id { get; private set; } + + /// + /// Gets the name of the resource. + /// + [JsonProperty("name")] + public string Name { get; private set; } + + /// + /// Gets the type of the resource. + /// + [JsonProperty("type")] + public string Type { get; private set; } + + /// + /// Required. Gets or sets the location of the resource. + /// + [JsonProperty("location")] + public string Location { get; set; } + + /// + /// Optional. Gets or sets the tags attached to the resource. + /// + [JsonProperty("tags")] + public IDictionary Tags { get; set; } + + /// + /// Validate the object. Throws ArgumentException or ArgumentNullException if validation fails. + /// + public virtual void Validate() + { + if (Location == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "Location"); + } + } + + private string _hostName; + + /// + /// Optional. + /// + public string HostName + { + get { return this._hostName; } + set { this._hostName = value; } + } + + private int? _port; + + /// + /// Optional. + /// + public int? Port + { + get { return this._port; } + set { this._port = value; } + } + + private int? _sslPort; + + /// + /// Optional. + /// + public int? SslPort + { + get { return this._sslPort; } + set { this._sslPort = value; } + } + + /// + /// Optional. + /// + [JsonProperty("properties.provisioningState")] + public string ProvisioningState { get; set; } + } + + public partial class RedisSubResource : IResource + { + /// + /// Gets the ID of the resource. + /// + [JsonProperty("id")] + public string Id { get; private set; } + + private string _hostName; + + /// + /// Optional. + /// + public string HostName + { + get { return this._hostName; } + set { this._hostName = value; } + } + + private int? _port; + + /// + /// Optional. + /// + public int? Port + { + get { return this._port; } + set { this._port = value; } + } + + private int? _sslPort; + + /// + /// Optional. + /// + public int? SslPort + { + get { return this._sslPort; } + set { this._sslPort = value; } + } + } + + public partial class RedisProperties + { + private bool? _enableNonSslPort; + + /// + /// Optional. + /// + public bool? EnableNonSslPort + { + get { return this._enableNonSslPort; } + set { this._enableNonSslPort = value; } + } + + private string _maxMemoryPolicy; + + /// + /// Optional. + /// + public string MaxMemoryPolicy + { + get { return this._maxMemoryPolicy; } + set { this._maxMemoryPolicy = value; } + } + + private string _redisVersion; + + /// + /// Optional. + /// + public string RedisVersion + { + get { return this._redisVersion; } + set { this._redisVersion = value; } + } + + private Sku _sku; + + /// + /// Optional. + /// + public Sku Sku + { + get { return this._sku; } + set { this._sku = value; } + } + } + + public partial class RedisReadableProperties + { + private string _hostName; + + /// + /// Optional. + /// + public string HostName + { + get { return this._hostName; } + set { this._hostName = value; } + } + + private int? _port; + + /// + /// Optional. + /// + public int? Port + { + get { return this._port; } + set { this._port = value; } + } + + private string _provisioningState; + + /// + /// Optional. + /// + public string ProvisioningState + { + get { return this._provisioningState; } + set { this._provisioningState = value; } + } + + private int? _sslPort; + + /// + /// Optional. + /// + public int? SslPort + { + get { return this._sslPort; } + set { this._sslPort = value; } + } + } + + public partial class Sku + { + private int? _capacity; + + /// + /// Optional. + /// + public int? Capacity + { + get { return this._capacity; } + set { this._capacity = value; } + } + + private string _family; + + /// + /// Optional. + /// + public string Family + { + get { return this._family; } + set { this._family = value; } + } + + private string _name; + + /// + /// Optional. + /// + public string Name + { + get { return this._name; } + set { this._name = value; } + } + } +} + +namespace Microsoft.Azure.Management.Redis +{ + public static partial class RedisManagementClientExtensions + { + } + + public partial interface IRedisManagementClient : IDisposable + { + string ApiVersion + { + get; + set; + } + + /// + /// The base URI of the service. + /// + Uri BaseUri + { + get; + set; + } + + ServiceClientCredentials Credentials + { + get; + set; + } + + int? LongRunningOperationInitialTimeout + { + get; + set; + } + + int? LongRunningOperationRetryTimeout + { + get; + set; + } + + IRedisOperations RedisOperations + { + get; + } + } + + public partial class RedisManagementClient : ServiceClient, IRedisManagementClient, IAzureClient + { + private string _apiVersion; + + public string ApiVersion + { + get { return this._apiVersion; } + set { this._apiVersion = value; } + } + + private Uri _baseUri; + + /// + /// The base URI of the service. + /// + public Uri BaseUri + { + get { return this._baseUri; } + set { this._baseUri = value; } + } + + /// + /// Gets or sets json serialization settings. + /// + public JsonSerializerSettings SerializationSettings { get; private set; } + + /// + /// Gets or sets json deserialization settings. + /// + public JsonSerializerSettings DeserializationSettings { get; private set; } + + private ServiceClientCredentials _credentials; + + public ServiceClientCredentials Credentials + { + get { return this._credentials; } + set { this._credentials = value; } + } + + private int? _longRunningOperationInitialTimeout; + + public int? LongRunningOperationInitialTimeout + { + get { return this._longRunningOperationInitialTimeout; } + set { this._longRunningOperationInitialTimeout = value; } + } + + private int? _longRunningOperationRetryTimeout; + + public int? LongRunningOperationRetryTimeout + { + get { return this._longRunningOperationRetryTimeout; } + set { this._longRunningOperationRetryTimeout = value; } + } + + public bool? GenerateClientRequestId { get; set; } + + private IRedisOperations _redisOperations; + + public virtual IRedisOperations RedisOperations + { + get { return this._redisOperations; } + } + + /// + /// Initializes a new instance of the RedisManagementClient class. + /// + public RedisManagementClient() + : base() + { + this._redisOperations = new RedisOperations(this); + this._baseUri = new Uri("https://management.azure.com"); + this._apiVersion = "2014-04-01-preview"; + this.HttpClient.Timeout = TimeSpan.FromSeconds(300); + this.Initialize(); + } + + /// + /// Initializes a new instance of the RedisManagementClient class. + /// + /// + /// Optional. The set of delegating handlers to insert in the http + /// client pipeline. + /// + public RedisManagementClient(params DelegatingHandler[] handlers) + : base(handlers) + { + this._redisOperations = new RedisOperations(this); + this._baseUri = new Uri("https://management.azure.com"); + this._apiVersion = "2014-04-01-preview"; + this.HttpClient.Timeout = TimeSpan.FromSeconds(300); + this.Initialize(); + } + + /// + /// Initializes a new instance of the RedisManagementClient class. + /// + /// + /// Optional. The http client handler used to handle http transport. + /// + /// + /// Optional. The set of delegating handlers to insert in the http + /// client pipeline. + /// + public RedisManagementClient(HttpClientHandler rootHandler, params DelegatingHandler[] handlers) + : base(rootHandler, handlers) + { + this._redisOperations = new RedisOperations(this); + this._baseUri = new Uri("https://management.azure.com"); + this._apiVersion = "2014-04-01-preview"; + this.HttpClient.Timeout = TimeSpan.FromSeconds(300); + this.Initialize(); + } + + /// + /// Initializes a new instance of the RedisManagementClient class. + /// + /// + /// Optional. The base URI of the service. + /// + /// + /// Optional. The set of delegating handlers to insert in the http + /// client pipeline. + /// + public RedisManagementClient(Uri baseUri, params DelegatingHandler[] handlers) + : this(handlers) + { + if (baseUri == null) + { + throw new ArgumentNullException("baseUri"); + } + this._baseUri = baseUri; + } + + /// + /// Initializes a new instance of the RedisManagementClient class. + /// + /// + /// Required. + /// + /// + /// Optional. The set of delegating handlers to insert in the http + /// client pipeline. + /// + public RedisManagementClient(ServiceClientCredentials credentials, params DelegatingHandler[] handlers) + : this(handlers) + { + if (credentials == null) + { + throw new ArgumentNullException("credentials"); + } + this._credentials = credentials; + + if (this.Credentials != null) + { + this.Credentials.InitializeServiceClient(this); + } + } + + /// + /// Initializes a new instance of the RedisManagementClient class. + /// + /// + /// Optional. The base URI of the service. + /// + /// + /// Required. + /// + /// + /// Optional. The set of delegating handlers to insert in the http + /// client pipeline. + /// + public RedisManagementClient(Uri baseUri, ServiceClientCredentials credentials, params DelegatingHandler[] handlers) + : this(handlers) + { + if (baseUri == null) + { + throw new ArgumentNullException("baseUri"); + } + if (credentials == null) + { + throw new ArgumentNullException("credentials"); + } + this._baseUri = baseUri; + this._credentials = credentials; + + if (this.Credentials != null) + { + this.Credentials.InitializeServiceClient(this); + } + } + + /// + /// Initializes client properties. + /// + private void Initialize() + { + SerializationSettings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + SerializationSettings.Converters.Add(new ResourceJsonConverter()); + DeserializationSettings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + DeserializationSettings.Converters.Add(new ResourceJsonConverter()); + DeserializationSettings.Converters.Add(new CloudErrorJsonConverter()); + } + } + + public static partial class RedisOperationsExtensions + { + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + public static RedisResource BeginCreateOrUpdate(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId) + { + return Task.Factory.StartNew((object s) => + { + return ((IRedisOperations)s).BeginCreateOrUpdateAsync(resourceGroupName, name, parameters, subscriptionId); + } + , operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + public static RedisSubResource BeginCreateOrUpdateSubResource(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId) + { + return Task.Factory.StartNew((object s) => + { + return ((IRedisOperations)s).BeginCreateOrUpdateSubResourceAsync(resourceGroupName, name, parameters, subscriptionId); + } + , operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + public static Sku BeginCreateOrUpdateNonResource(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId) + { + return Task.Factory.StartNew((object s) => + { + return ((IRedisOperations)s).BeginCreateOrUpdateNonResourceAsync(resourceGroupName, name, parameters, subscriptionId); + } + , operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public static async Task BeginCreateOrUpdateNonResourceAsync(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + AzureOperationResponse result = await operations.BeginCreateOrUpdateNonResourceWithHttpMessagesAsync(resourceGroupName, name, parameters, subscriptionId, cancellationToken).ConfigureAwait(false); + return result.Body; + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public static async Task BeginCreateOrUpdateSubResourceAsync(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + AzureOperationResponse result = await operations.BeginCreateOrUpdateSubResourceWithHttpMessagesAsync(resourceGroupName, name, parameters, subscriptionId, cancellationToken).ConfigureAwait(false); + return result.Body; + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public static async Task BeginCreateOrUpdateAsync(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + AzureOperationResponse result = await operations.BeginCreateOrUpdateWithHttpMessagesAsync(resourceGroupName, name, parameters, subscriptionId, cancellationToken).ConfigureAwait(false); + return result.Body; + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + public static RedisResource CreateOrUpdate(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId) + { + return Task.Factory.StartNew((object s) => + { + return ((IRedisOperations)s).CreateOrUpdateAsync(resourceGroupName, name, parameters, subscriptionId); + } + , operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + public static Sku CreateOrUpdateNonResource(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId) + { + return Task.Factory.StartNew((object s) => + { + return ((IRedisOperations)s).CreateOrUpdateNonResourceAsync(resourceGroupName, name, parameters, subscriptionId); + } + , operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + public static RedisSubResource CreateOrUpdateSubResource(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId) + { + return Task.Factory.StartNew((object s) => + { + return ((IRedisOperations)s).CreateOrUpdateSubResourceAsync(resourceGroupName, name, parameters, subscriptionId); + } + , operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public static async Task CreateOrUpdateAsync(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + AzureOperationResponse result = await operations.CreateOrUpdateWithHttpMessagesAsync(resourceGroupName, name, parameters, subscriptionId, cancellationToken).ConfigureAwait(false); + return result.Body; + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public static async Task CreateOrUpdateNonResourceAsync(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + AzureOperationResponse result = await operations.CreateOrUpdateNonResourceWithHttpMessagesAsync(resourceGroupName, name, parameters, subscriptionId, cancellationToken).ConfigureAwait(false); + return result.Body; + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public static async Task CreateOrUpdateSubResourceAsync(this IRedisOperations operations, string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + AzureOperationResponse result = await operations.CreateOrUpdateSubResourceWithHttpMessagesAsync(resourceGroupName, name, parameters, subscriptionId, cancellationToken).ConfigureAwait(false); + return result.Body; + } + + /// + /// Deletes a redis cache. This operation takes a while to complete. + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + public static void BeginDelete(this IRedisOperations operations, string resourceGroupName, string name, string subscriptionId) + { + Task.Factory.StartNew((object s) => + { + return ((IRedisOperations)s).BeginDeleteAsync(resourceGroupName, name, subscriptionId); + } + , operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + public static void Delete(this IRedisOperations operations, string resourceGroupName, string name, string subscriptionId) + { + Task.Factory.StartNew((object s) => + { + return ((IRedisOperations)s).DeleteAsync(resourceGroupName, name, subscriptionId); + } + , operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + /// + /// Deletes a redis cache. This operation takes a while to complete. + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public static async Task BeginDeleteAsync(this IRedisOperations operations, string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + await operations.BeginDeleteWithHttpMessagesAsync(resourceGroupName, name, subscriptionId, cancellationToken).ConfigureAwait(false); + return; + } + public static async Task DeleteAsync(this IRedisOperations operations, string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + await operations.DeleteWithHttpMessagesAsync(resourceGroupName, name, subscriptionId, cancellationToken).ConfigureAwait(false); + return; + } + + public static Sku BeginPost(this IRedisOperations operations, string resourceGroupName, string name, string subscriptionId) + { + return Task.Factory.StartNew((object s) => + { + return ((IRedisOperations)s).BeginPostAsync(resourceGroupName, name, subscriptionId); + } + , operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + public static Sku Post(this IRedisOperations operations, string resourceGroupName, string name, string subscriptionId) + { + return Task.Factory.StartNew((object s) => + { + return ((IRedisOperations)s).PostAsync(resourceGroupName, name, subscriptionId); + } + , operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + public static async Task BeginPostAsync(this IRedisOperations operations, string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + AzureOperationResponse result = await operations.BeginPostWithHttpMessagesAsync(resourceGroupName, name, subscriptionId, cancellationToken).ConfigureAwait(false); + return result.Body; + } + public static async Task PostAsync(this IRedisOperations operations, string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + AzureOperationResponse result = await operations.PostWithHttpMessagesAsync(resourceGroupName, name, subscriptionId, cancellationToken).ConfigureAwait(false); + return result.Body; + } + + + /// + /// Gets a redis cache (resource description). + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + public static RedisResource Get(this IRedisOperations operations, string resourceGroupName, string name, string subscriptionId) + { + return Task.Factory.StartNew((object s) => + { + return ((IRedisOperations)s).GetAsync(resourceGroupName, name, subscriptionId); + } + , operations, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap().GetAwaiter().GetResult(); + } + + /// + /// Gets a redis cache (resource description). + /// + /// + /// Reference to the Microsoft.Azure.Management.Redis.IRedisOperations. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public static async Task GetAsync(this IRedisOperations operations, string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + AzureOperationResponse result = await operations.GetWithHttpMessagesAsync(resourceGroupName, name, subscriptionId, cancellationToken).ConfigureAwait(false); + return result.Body; + } + } + + public partial interface IRedisOperations + { + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + Task> BeginCreateOrUpdateWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + + Task> CreateOrUpdateWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + Task> BeginCreateOrUpdateSubResourceWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + + Task> CreateOrUpdateSubResourceWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + Task> BeginCreateOrUpdateNonResourceWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + + Task> CreateOrUpdateNonResourceWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + + /// + /// Deletes a redis cache. This operation takes a while to complete. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + Task BeginDeleteWithHttpMessagesAsync(string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + Task DeleteWithHttpMessagesAsync(string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + + /// + /// Gets a redis cache (resource description). + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + Task> GetWithHttpMessagesAsync(string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + + Task> BeginPostWithHttpMessagesAsync(string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + Task> PostWithHttpMessagesAsync(string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)); + } + + internal partial class RedisOperations : IServiceOperations, IRedisOperations + { + /// + /// Initializes a new instance of the RedisOperations class. + /// + /// + /// Reference to the service client. + /// + internal RedisOperations(RedisManagementClient client) + { + this._client = client; + } + + private RedisManagementClient _client; + + /// + /// Gets a reference to the + /// Microsoft.Azure.Management.Redis.RedisManagementClient. + /// + public RedisManagementClient Client + { + get { return this._client; } + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public async Task> BeginCreateOrUpdateWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Validate + if (resourceGroupName == null) + { + throw new ArgumentNullException("resourceGroupName"); + } + if (name == null) + { + throw new ArgumentNullException("name"); + } + if (parameters == null) + { + throw new ArgumentNullException("parameters"); + } + if (subscriptionId == null) + { + throw new ArgumentNullException("subscriptionId"); + } + + // Tracing + bool shouldTrace = ServiceClientTracing.IsEnabled; + string invocationId = null; + if (shouldTrace) + { + invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("resourceGroupName", resourceGroupName); + tracingParameters.Add("name", name); + tracingParameters.Add("parameters", parameters); + tracingParameters.Add("subscriptionId", subscriptionId); + ServiceClientTracing.Enter(invocationId, this, "BeginCreateOrUpdateAsync", tracingParameters); + } + + // Construct URL + string url = ""; + url = url + "/subscriptions/"; + url = url + Uri.EscapeDataString(subscriptionId); + url = url + "/resourceGroups/"; + url = url + Uri.EscapeDataString(resourceGroupName); + url = url + "/providers/Microsoft.Cache/Redis/"; + url = url + Uri.EscapeDataString(name); + string baseUrl = this.Client.BaseUri.AbsoluteUri; + // Trim '/' character from the end of baseUrl and beginning of url. + if (baseUrl[baseUrl.Length - 1] == '/') + { + baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); + } + if (url[0] == '/') + { + url = url.Substring(1); + } + url = baseUrl + "/" + url; + url = url.Replace(" ", "%20"); + + // Create HTTP transport objects + HttpRequestMessage httpRequest = new HttpRequestMessage(); + httpRequest.Method = HttpMethod.Put; + httpRequest.RequestUri = new Uri(url); + + // Set Headers + + // Set Credentials + if (this.Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await this.Client.Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false); + } + + // Serialize Request + string requestContent = JsonConvert.SerializeObject(parameters, this.Client.SerializationSettings); + httpRequest.Content = new StringContent(requestContent, Encoding.UTF8); + httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); + + // Send Request + if (shouldTrace) + { + ServiceClientTracing.SendRequest(invocationId, httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + HttpResponseMessage httpResponse = await this.Client.HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); + if (shouldTrace) + { + ServiceClientTracing.ReceiveResponse(invocationId, httpResponse); + } + HttpStatusCode statusCode = httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + if (statusCode != HttpStatusCode.OK && statusCode != HttpStatusCode.Created && statusCode != HttpStatusCode.Accepted) + { + CloudError error = JsonConvert.DeserializeObject(responseContent, Client.DeserializationSettings); + CloudException ex = new CloudException(); + if (error != null) + { + ex = new CloudException(error.Message); + } + ex.Request = new HttpRequestMessageWrapper(httpRequest, requestContent); + ex.Response = new HttpResponseMessageWrapper(httpResponse, responseContent); + + if (shouldTrace) + { + ServiceClientTracing.Error(invocationId, ex); + } + throw ex; + } + + // Create Result + AzureOperationResponse result = new AzureOperationResponse(); + result.Request = httpRequest; + result.Response = httpResponse; + + // Deserialize Response + if (statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Created) + { + RedisResource resultModel = JsonConvert.DeserializeObject(responseContent, this.Client.DeserializationSettings); + result.Body = resultModel; + } + + if (shouldTrace) + { + ServiceClientTracing.Exit(invocationId, result); + } + return result; + } + + public async Task> CreateOrUpdateWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Send Request + AzureOperationResponse response = await BeginCreateOrUpdateWithHttpMessagesAsync( + resourceGroupName, + name, + parameters, + subscriptionId, + cancellationToken); + + Debug.Assert(response.Response.StatusCode == HttpStatusCode.OK || + response.Response.StatusCode == HttpStatusCode.Created || + response.Response.StatusCode == HttpStatusCode.Accepted); + + return await this.Client.GetPutOrPatchOperationResultAsync(response, + null, + cancellationToken); + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public async Task> BeginCreateOrUpdateNonResourceWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Validate + if (resourceGroupName == null) + { + throw new ArgumentNullException("resourceGroupName"); + } + if (name == null) + { + throw new ArgumentNullException("name"); + } + if (parameters == null) + { + throw new ArgumentNullException("parameters"); + } + if (subscriptionId == null) + { + throw new ArgumentNullException("subscriptionId"); + } + + // Tracing + bool shouldTrace = ServiceClientTracing.IsEnabled; + string invocationId = null; + if (shouldTrace) + { + invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("resourceGroupName", resourceGroupName); + tracingParameters.Add("name", name); + tracingParameters.Add("parameters", parameters); + tracingParameters.Add("subscriptionId", subscriptionId); + ServiceClientTracing.Enter(invocationId, this, "BeginCreateOrUpdateAsync", tracingParameters); + } + + // Construct URL + string url = ""; + url = url + "/subscriptions/"; + url = url + Uri.EscapeDataString(subscriptionId); + url = url + "/resourceGroups/"; + url = url + Uri.EscapeDataString(resourceGroupName); + url = url + "/providers/Microsoft.Cache/Redis/"; + url = url + Uri.EscapeDataString(name); + string baseUrl = this.Client.BaseUri.AbsoluteUri; + // Trim '/' character from the end of baseUrl and beginning of url. + if (baseUrl[baseUrl.Length - 1] == '/') + { + baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); + } + if (url[0] == '/') + { + url = url.Substring(1); + } + url = baseUrl + "/" + url; + url = url.Replace(" ", "%20"); + + // Create HTTP transport objects + HttpRequestMessage httpRequest = new HttpRequestMessage(); + httpRequest.Method = HttpMethod.Put; + httpRequest.RequestUri = new Uri(url); + + // Set Headers + + // Set Credentials + if (this.Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await this.Client.Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false); + } + + // Serialize Request + string requestContent = JsonConvert.SerializeObject(parameters, this.Client.SerializationSettings); + httpRequest.Content = new StringContent(requestContent, Encoding.UTF8); + httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); + + // Send Request + if (shouldTrace) + { + ServiceClientTracing.SendRequest(invocationId, httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + HttpResponseMessage httpResponse = await this.Client.HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); + if (shouldTrace) + { + ServiceClientTracing.ReceiveResponse(invocationId, httpResponse); + } + HttpStatusCode statusCode = httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + if (statusCode != HttpStatusCode.OK && statusCode != HttpStatusCode.Created && statusCode != HttpStatusCode.Accepted) + { + CloudError error = JsonConvert.DeserializeObject(responseContent, Client.DeserializationSettings); + CloudException ex = new CloudException(); + if (error != null) + { + ex = new CloudException(error.Message); + } + ex.Request = new HttpRequestMessageWrapper(httpRequest, requestContent); + ex.Response = new HttpResponseMessageWrapper(httpResponse, responseContent); + + if (shouldTrace) + { + ServiceClientTracing.Error(invocationId, ex); + } + throw ex; + } + + // Create Result + AzureOperationResponse result = new AzureOperationResponse(); + result.Request = httpRequest; + result.Response = httpResponse; + + // Deserialize Response + if (statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Created) + { + Sku resultModel = JsonConvert.DeserializeObject(responseContent, this.Client.DeserializationSettings); + result.Body = resultModel; + } + + if (shouldTrace) + { + ServiceClientTracing.Exit(invocationId, result); + } + return result; + } + + public async Task> CreateOrUpdateNonResourceWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Send Request + AzureOperationResponse response = await BeginCreateOrUpdateNonResourceWithHttpMessagesAsync( + resourceGroupName, + name, + parameters, + subscriptionId, + cancellationToken); + + Debug.Assert(response.Response.StatusCode == HttpStatusCode.OK || + response.Response.StatusCode == HttpStatusCode.Created || + response.Response.StatusCode == HttpStatusCode.Accepted); + + return await this.Client.GetPutOrPatchOperationResultAsync(response, + null, + cancellationToken); + } + + /// + /// Create a redis cache, or replace (overwrite/recreate, with + /// potential downtime) an existing cache + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public async Task> BeginCreateOrUpdateSubResourceWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Validate + if (resourceGroupName == null) + { + throw new ArgumentNullException("resourceGroupName"); + } + if (name == null) + { + throw new ArgumentNullException("name"); + } + if (parameters == null) + { + throw new ArgumentNullException("parameters"); + } + if (subscriptionId == null) + { + throw new ArgumentNullException("subscriptionId"); + } + + // Tracing + bool shouldTrace = ServiceClientTracing.IsEnabled; + string invocationId = null; + if (shouldTrace) + { + invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("resourceGroupName", resourceGroupName); + tracingParameters.Add("name", name); + tracingParameters.Add("parameters", parameters); + tracingParameters.Add("subscriptionId", subscriptionId); + ServiceClientTracing.Enter(invocationId, this, "BeginCreateOrUpdateAsync", tracingParameters); + } + + // Construct URL + string url = ""; + url = url + "/subscriptions/"; + url = url + Uri.EscapeDataString(subscriptionId); + url = url + "/resourceGroups/"; + url = url + Uri.EscapeDataString(resourceGroupName); + url = url + "/providers/Microsoft.Cache/Redis/"; + url = url + Uri.EscapeDataString(name); + string baseUrl = this.Client.BaseUri.AbsoluteUri; + // Trim '/' character from the end of baseUrl and beginning of url. + if (baseUrl[baseUrl.Length - 1] == '/') + { + baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); + } + if (url[0] == '/') + { + url = url.Substring(1); + } + url = baseUrl + "/" + url; + url = url.Replace(" ", "%20"); + + // Create HTTP transport objects + HttpRequestMessage httpRequest = new HttpRequestMessage(); + httpRequest.Method = HttpMethod.Put; + httpRequest.RequestUri = new Uri(url); + + // Set Headers + + // Set Credentials + if (this.Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await this.Client.Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false); + } + + // Serialize Request + string requestContent = JsonConvert.SerializeObject(parameters, this.Client.SerializationSettings); + httpRequest.Content = new StringContent(requestContent, Encoding.UTF8); + httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); + + // Send Request + if (shouldTrace) + { + ServiceClientTracing.SendRequest(invocationId, httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + HttpResponseMessage httpResponse = await this.Client.HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); + if (shouldTrace) + { + ServiceClientTracing.ReceiveResponse(invocationId, httpResponse); + } + HttpStatusCode statusCode = httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + if (statusCode != HttpStatusCode.OK && statusCode != HttpStatusCode.Created && statusCode != HttpStatusCode.Accepted) + { + CloudError error = JsonConvert.DeserializeObject(responseContent, Client.DeserializationSettings); + CloudException ex = new CloudException(); + if (error != null) + { + ex = new CloudException(error.Message); + } + ex.Request = new HttpRequestMessageWrapper(httpRequest, requestContent); + ex.Response = new HttpResponseMessageWrapper(httpResponse, responseContent); + + if (shouldTrace) + { + ServiceClientTracing.Error(invocationId, ex); + } + throw ex; + } + + // Create Result + AzureOperationResponse result = new AzureOperationResponse(); + result.Request = httpRequest; + result.Response = httpResponse; + + // Deserialize Response + if (statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Created) + { + RedisSubResource resultModel = JsonConvert.DeserializeObject(responseContent, this.Client.DeserializationSettings); + result.Body = resultModel; + } + + if (shouldTrace) + { + ServiceClientTracing.Exit(invocationId, result); + } + return result; + } + + public async Task> CreateOrUpdateSubResourceWithHttpMessagesAsync(string resourceGroupName, string name, RedisCreateOrUpdateParameters parameters, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Send Request + AzureOperationResponse response = await BeginCreateOrUpdateSubResourceWithHttpMessagesAsync( + resourceGroupName, + name, + parameters, + subscriptionId, + cancellationToken); + + Debug.Assert(response.Response.StatusCode == HttpStatusCode.OK || + response.Response.StatusCode == HttpStatusCode.Created || + response.Response.StatusCode == HttpStatusCode.Accepted); + + return await this.Client.GetPutOrPatchOperationResultAsync(response, + null, + cancellationToken); + } + + /// + /// Deletes a redis cache. This operation takes a while to complete. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public async Task BeginDeleteWithHttpMessagesAsync(string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Validate + if (resourceGroupName == null) + { + throw new ArgumentNullException("resourceGroupName"); + } + if (name == null) + { + throw new ArgumentNullException("name"); + } + if (subscriptionId == null) + { + throw new ArgumentNullException("subscriptionId"); + } + + // Tracing + bool shouldTrace = ServiceClientTracing.IsEnabled; + string invocationId = null; + if (shouldTrace) + { + invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("resourceGroupName", resourceGroupName); + tracingParameters.Add("name", name); + tracingParameters.Add("subscriptionId", subscriptionId); + ServiceClientTracing.Enter(invocationId, this, "BeginDeleteAsync", tracingParameters); + } + + // Construct URL + string url = ""; + url = url + "/subscriptions/"; + url = url + Uri.EscapeDataString(subscriptionId); + url = url + "/resourceGroups/"; + url = url + Uri.EscapeDataString(resourceGroupName); + url = url + "/providers/Microsoft.Cache/Redis/"; + url = url + Uri.EscapeDataString(name); + string baseUrl = this.Client.BaseUri.AbsoluteUri; + // Trim '/' character from the end of baseUrl and beginning of url. + if (baseUrl[baseUrl.Length - 1] == '/') + { + baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); + } + if (url[0] == '/') + { + url = url.Substring(1); + } + url = baseUrl + "/" + url; + url = url.Replace(" ", "%20"); + + // Create HTTP transport objects + HttpRequestMessage httpRequest = new HttpRequestMessage(); + httpRequest.Method = HttpMethod.Delete; + httpRequest.RequestUri = new Uri(url); + + // Set Credentials + if (this.Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await this.Client.Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false); + } + + // Send Request + if (shouldTrace) + { + ServiceClientTracing.SendRequest(invocationId, httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + HttpResponseMessage httpResponse = await this.Client.HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); + if (shouldTrace) + { + ServiceClientTracing.ReceiveResponse(invocationId, httpResponse); + } + HttpStatusCode statusCode = httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + if (statusCode != HttpStatusCode.OK && statusCode != HttpStatusCode.Accepted + && statusCode != HttpStatusCode.NotFound && statusCode != HttpStatusCode.NoContent) + { + CloudError error = JsonConvert.DeserializeObject(responseContent, Client.DeserializationSettings); + CloudException ex = new CloudException(); + if (error != null) + { + ex = new CloudException(error.Message); + } + ex.Request = new HttpRequestMessageWrapper(httpRequest, null); + ex.Response = new HttpResponseMessageWrapper(httpResponse, responseContent); + if (shouldTrace) + { + ServiceClientTracing.Error(invocationId, ex); + } + throw ex; + } + + // Create Result + AzureOperationResponse result = new AzureOperationResponse(); + result.Request = httpRequest; + result.Response = httpResponse; + + if (shouldTrace) + { + ServiceClientTracing.Exit(invocationId, result); + } + return result; + } + + public async Task DeleteWithHttpMessagesAsync( + string resourceGroupName, + string name, + string subscriptionId, + CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Send Request + AzureOperationResponse response = await BeginDeleteWithHttpMessagesAsync( + resourceGroupName, + name, + subscriptionId, + cancellationToken); + + Debug.Assert(response.Response.StatusCode == HttpStatusCode.OK || + response.Response.StatusCode == HttpStatusCode.Accepted || + response.Response.StatusCode == HttpStatusCode.Created || + response.Response.StatusCode == HttpStatusCode.NoContent); + + return await this.Client.GetPostOrDeleteOperationResultAsync(response, null, cancellationToken); + } + + public async Task> BeginPostWithHttpMessagesAsync(string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Validate + if (resourceGroupName == null) + { + throw new ArgumentNullException("resourceGroupName"); + } + if (name == null) + { + throw new ArgumentNullException("name"); + } + if (subscriptionId == null) + { + throw new ArgumentNullException("subscriptionId"); + } + + // Tracing + bool shouldTrace = ServiceClientTracing.IsEnabled; + string invocationId = null; + if (shouldTrace) + { + invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("resourceGroupName", resourceGroupName); + tracingParameters.Add("name", name); + tracingParameters.Add("subscriptionId", subscriptionId); + ServiceClientTracing.Enter(invocationId, this, "BeginDeleteAsync", tracingParameters); + } + + // Construct URL + string url = ""; + url = url + "/subscriptions/"; + url = url + Uri.EscapeDataString(subscriptionId); + url = url + "/resourceGroups/"; + url = url + Uri.EscapeDataString(resourceGroupName); + url = url + "/providers/Microsoft.Cache/Redis/"; + url = url + Uri.EscapeDataString(name); + string baseUrl = this.Client.BaseUri.AbsoluteUri; + // Trim '/' character from the end of baseUrl and beginning of url. + if (baseUrl[baseUrl.Length - 1] == '/') + { + baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); + } + if (url[0] == '/') + { + url = url.Substring(1); + } + url = baseUrl + "/" + url; + url = url.Replace(" ", "%20"); + + // Create HTTP transport objects + HttpRequestMessage httpRequest = new HttpRequestMessage(); + httpRequest.Method = HttpMethod.Post; + httpRequest.RequestUri = new Uri(url); + + // Set Credentials + if (this.Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await this.Client.Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false); + } + + // Send Request + if (shouldTrace) + { + ServiceClientTracing.SendRequest(invocationId, httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + HttpResponseMessage httpResponse = await this.Client.HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); + if (shouldTrace) + { + ServiceClientTracing.ReceiveResponse(invocationId, httpResponse); + } + HttpStatusCode statusCode = httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + if (statusCode != HttpStatusCode.OK && statusCode != HttpStatusCode.Accepted && statusCode != HttpStatusCode.NotFound) + { + CloudError error = JsonConvert.DeserializeObject(responseContent, Client.DeserializationSettings); + CloudException ex = new CloudException(); + if (error != null) + { + ex = new CloudException(error.Message); + } + ex.Request = new HttpRequestMessageWrapper(httpRequest, null); + ex.Response = new HttpResponseMessageWrapper(httpResponse, responseContent); + if (shouldTrace) + { + ServiceClientTracing.Error(invocationId, ex); + } + throw ex; + } + + // Create Result + AzureOperationResponse result = new AzureOperationResponse(); + result.Request = httpRequest; + result.Response = httpResponse; + result.Body = JsonConvert.DeserializeObject(responseContent, this.Client.DeserializationSettings); + + if (shouldTrace) + { + ServiceClientTracing.Exit(invocationId, result); + } + return result; + } + + public async Task> PostWithHttpMessagesAsync( + string resourceGroupName, + string name, + string subscriptionId, + CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Send Request + AzureOperationResponse response = await BeginPostWithHttpMessagesAsync( + resourceGroupName, + name, + subscriptionId, + cancellationToken); + + Debug.Assert(response.Response.StatusCode == HttpStatusCode.OK || + response.Response.StatusCode == HttpStatusCode.Accepted || + response.Response.StatusCode == HttpStatusCode.Created); + + return await this.Client.GetPostOrDeleteOperationResultAsync(response, null, cancellationToken); + } + + + + /// + /// Gets a redis cache (resource description). + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public async Task> GetWithHttpMessagesAsync(string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Validate + if (resourceGroupName == null) + { + throw new ArgumentNullException("resourceGroupName"); + } + if (name == null) + { + throw new ArgumentNullException("name"); + } + if (subscriptionId == null) + { + throw new ArgumentNullException("subscriptionId"); + } + + // Tracing + bool shouldTrace = ServiceClientTracing.IsEnabled; + string invocationId = null; + if (shouldTrace) + { + invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("resourceGroupName", resourceGroupName); + tracingParameters.Add("name", name); + tracingParameters.Add("subscriptionId", subscriptionId); + ServiceClientTracing.Enter(invocationId, this, "GetLongRunningOperationStatusAsync", tracingParameters); + } + + // Construct URL + string url = ""; + url = url + "/subscriptions/"; + url = url + Uri.EscapeDataString(subscriptionId); + url = url + "/resourceGroups/"; + url = url + Uri.EscapeDataString(resourceGroupName); + url = url + "/providers/Microsoft.Cache/Redis/"; + url = url + Uri.EscapeDataString(name); + string baseUrl = this.Client.BaseUri.AbsoluteUri; + // Trim '/' character from the end of baseUrl and beginning of url. + if (baseUrl[baseUrl.Length - 1] == '/') + { + baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); + } + if (url[0] == '/') + { + url = url.Substring(1); + } + url = baseUrl + "/" + url; + url = url.Replace(" ", "%20"); + + // Create HTTP transport objects + HttpRequestMessage httpRequest = new HttpRequestMessage(); + httpRequest.Method = HttpMethod.Get; + httpRequest.RequestUri = new Uri(url); + + // Set Credentials + if (this.Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await this.Client.Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false); + } + + // Send Request + if (shouldTrace) + { + ServiceClientTracing.SendRequest(invocationId, httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + HttpResponseMessage httpResponse = await this.Client.HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); + if (shouldTrace) + { + ServiceClientTracing.ReceiveResponse(invocationId, httpResponse); + } + HttpStatusCode statusCode = httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + if (statusCode != HttpStatusCode.OK) + { + CloudError error = JsonConvert.DeserializeObject(responseContent, Client.DeserializationSettings); + CloudException ex = new CloudException(); + if (error != null) + { + ex = new CloudException(error.Message); + } + ex.Request = new HttpRequestMessageWrapper(httpRequest, null); + ex.Response = new HttpResponseMessageWrapper(httpResponse, responseContent); + if (shouldTrace) + { + ServiceClientTracing.Error(invocationId, ex); + } + throw ex; + } + + // Create Result + AzureOperationResponse result = new AzureOperationResponse(); + result.Request = httpRequest; + result.Response = httpResponse; + + // Deserialize Response + if (statusCode == HttpStatusCode.OK) + { + RedisResource resultModel = JsonConvert.DeserializeObject(responseContent, this.Client.DeserializationSettings); + result.Body = resultModel; + } + + if (shouldTrace) + { + ServiceClientTracing.Exit(invocationId, result); + } + return result; + } + + /// + /// Gets a redis cache (resource description). + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public async Task> GetSkuWithHttpMessagesAsync(string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Validate + if (resourceGroupName == null) + { + throw new ArgumentNullException("resourceGroupName"); + } + if (name == null) + { + throw new ArgumentNullException("name"); + } + if (subscriptionId == null) + { + throw new ArgumentNullException("subscriptionId"); + } + + // Tracing + bool shouldTrace = ServiceClientTracing.IsEnabled; + string invocationId = null; + if (shouldTrace) + { + invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("resourceGroupName", resourceGroupName); + tracingParameters.Add("name", name); + tracingParameters.Add("subscriptionId", subscriptionId); + ServiceClientTracing.Enter(invocationId, this, "GetLongRunningOperationStatusAsync", tracingParameters); + } + + // Construct URL + string url = ""; + url = url + "/subscriptions/"; + url = url + Uri.EscapeDataString(subscriptionId); + url = url + "/resourceGroups/"; + url = url + Uri.EscapeDataString(resourceGroupName); + url = url + "/providers/Microsoft.Cache/Redis/"; + url = url + Uri.EscapeDataString(name); + string baseUrl = this.Client.BaseUri.AbsoluteUri; + // Trim '/' character from the end of baseUrl and beginning of url. + if (baseUrl[baseUrl.Length - 1] == '/') + { + baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); + } + if (url[0] == '/') + { + url = url.Substring(1); + } + url = baseUrl + "/" + url; + url = url.Replace(" ", "%20"); + + // Create HTTP transport objects + HttpRequestMessage httpRequest = new HttpRequestMessage(); + httpRequest.Method = HttpMethod.Get; + httpRequest.RequestUri = new Uri(url); + + // Set Credentials + if (this.Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await this.Client.Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false); + } + + // Send Request + if (shouldTrace) + { + ServiceClientTracing.SendRequest(invocationId, httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + HttpResponseMessage httpResponse = await this.Client.HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); + if (shouldTrace) + { + ServiceClientTracing.ReceiveResponse(invocationId, httpResponse); + } + HttpStatusCode statusCode = httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + if (statusCode != HttpStatusCode.OK) + { + CloudError error = JsonConvert.DeserializeObject(responseContent, Client.DeserializationSettings); + CloudException ex = new CloudException(); + if (error != null) + { + ex = new CloudException(error.Message); + } + ex.Request = new HttpRequestMessageWrapper(httpRequest, null); + ex.Response = new HttpResponseMessageWrapper(httpResponse, responseContent); + if (shouldTrace) + { + ServiceClientTracing.Error(invocationId, ex); + } + throw ex; + } + + // Create Result + AzureOperationResponse result = new AzureOperationResponse(); + result.Request = httpRequest; + result.Response = httpResponse; + + // Deserialize Response + if (statusCode == HttpStatusCode.OK) + { + Sku resultModel = JsonConvert.DeserializeObject(responseContent, this.Client.DeserializationSettings); + result.Body = resultModel; + } + + if (shouldTrace) + { + ServiceClientTracing.Exit(invocationId, result); + } + return result; + } + + /// + /// Gets a redis cache (resource description). + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Required. + /// + /// + /// Cancellation token. + /// + public async Task> GetSubResourceWithHttpMessagesAsync(string resourceGroupName, string name, string subscriptionId, CancellationToken cancellationToken = default(System.Threading.CancellationToken)) + { + // Validate + if (resourceGroupName == null) + { + throw new ArgumentNullException("resourceGroupName"); + } + if (name == null) + { + throw new ArgumentNullException("name"); + } + if (subscriptionId == null) + { + throw new ArgumentNullException("subscriptionId"); + } + + // Tracing + bool shouldTrace = ServiceClientTracing.IsEnabled; + string invocationId = null; + if (shouldTrace) + { + invocationId = ServiceClientTracing.NextInvocationId.ToString(); + Dictionary tracingParameters = new Dictionary(); + tracingParameters.Add("resourceGroupName", resourceGroupName); + tracingParameters.Add("name", name); + tracingParameters.Add("subscriptionId", subscriptionId); + ServiceClientTracing.Enter(invocationId, this, "GetLongRunningOperationStatusAsync", tracingParameters); + } + + // Construct URL + string url = ""; + url = url + "/subscriptions/"; + url = url + Uri.EscapeDataString(subscriptionId); + url = url + "/resourceGroups/"; + url = url + Uri.EscapeDataString(resourceGroupName); + url = url + "/providers/Microsoft.Cache/Redis/"; + url = url + Uri.EscapeDataString(name); + string baseUrl = this.Client.BaseUri.AbsoluteUri; + // Trim '/' character from the end of baseUrl and beginning of url. + if (baseUrl[baseUrl.Length - 1] == '/') + { + baseUrl = baseUrl.Substring(0, baseUrl.Length - 1); + } + if (url[0] == '/') + { + url = url.Substring(1); + } + url = baseUrl + "/" + url; + url = url.Replace(" ", "%20"); + + // Create HTTP transport objects + HttpRequestMessage httpRequest = new HttpRequestMessage(); + httpRequest.Method = HttpMethod.Get; + httpRequest.RequestUri = new Uri(url); + + // Set Credentials + if (this.Client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await this.Client.Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false); + } + + // Send Request + if (shouldTrace) + { + ServiceClientTracing.SendRequest(invocationId, httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + HttpResponseMessage httpResponse = await this.Client.HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); + if (shouldTrace) + { + ServiceClientTracing.ReceiveResponse(invocationId, httpResponse); + } + HttpStatusCode statusCode = httpResponse.StatusCode; + cancellationToken.ThrowIfCancellationRequested(); + string responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + if (statusCode != HttpStatusCode.OK) + { + CloudError error = JsonConvert.DeserializeObject(responseContent, Client.DeserializationSettings); + CloudException ex = new CloudException(); + if (error != null) + { + ex = new CloudException(error.Message); + } + ex.Request = new HttpRequestMessageWrapper(httpRequest, null); + ex.Response = new HttpResponseMessageWrapper(httpResponse, responseContent); + if (shouldTrace) + { + ServiceClientTracing.Error(invocationId, ex); + } + throw ex; + } + + // Create Result + AzureOperationResponse result = new AzureOperationResponse(); + result.Request = httpRequest; + result.Response = httpResponse; + + // Deserialize Response + if (statusCode == HttpStatusCode.OK) + { + RedisSubResource resultModel = JsonConvert.DeserializeObject(responseContent, this.Client.DeserializationSettings); + result.Body = resultModel; + } + + if (shouldTrace) + { + ServiceClientTracing.Exit(invocationId, result); + } + return result; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/SampleResource.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/SampleResource.cs new file mode 100644 index 0000000000000..aa327fd1a9235 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/SampleResource.cs @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Microsoft.Rest.Azure +{ + /// + /// Information for resource. + /// + public partial class SampleResource : IResource + { + /// + /// Gets the ID of the resource. + /// + [JsonProperty("id")] + public string Id { get; private set; } + + /// + /// Gets the name of the resource. + /// + [JsonProperty("name")] + public string Name { get; private set; } + + /// + /// Gets the type of the resource. + /// + [JsonProperty("type")] + public string Type { get; private set; } + + /// + /// Required. Gets or sets the location of the resource. + /// + [JsonProperty("location")] + public string Location { get; set; } + + /// + /// Optional. Gets or sets the tags attached to the resource. + /// + [JsonProperty("tags")] + public IDictionary Tags { get; set; } + + /// + /// Optional. Gets or sets the size of the resource. + /// + [JsonProperty("properties.size")] + public string Size { get; set; } + + /// + /// Optional. Gets or sets the child resource. + /// + [JsonProperty("properties.child")] + public SampleResourceChild Child { get; set; } + + /// + /// Optional. Gets or sets the details. + /// + [JsonProperty("properties.name")] + public dynamic Details { get; set; } + + /// + /// Optional. Gets or sets the plan. + /// + [JsonProperty("plan")] + public string Plan { get; set; } + + /// + /// Optional. Gets or sets the provisioning state. + /// + [JsonProperty("properties.provisioningState")] + public string ProvisioningState { get; set; } + + /// + /// Validate the object. Throws ArgumentException or ArgumentNullException if validation fails. + /// + public virtual void Validate() + { + if (Location == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "Location"); + } + } + + } + + /// + /// Information for resource. + /// + public abstract class SampleResourceChild : IResource + { + /// + /// Gets the ID of the resource. + /// + [JsonProperty("id")] + public string Id { get; private set; } + } + + /// + /// Information for resource. + /// + public partial class SampleResourceChild1 : SampleResourceChild + { + /// + /// Optional. Gets or sets the Id of the resource. + /// + [JsonProperty("properties.name1")] + public string ChildName1 { get; set; } + } + + /// + /// Information for resource. + /// + public partial class SampleResourceChild2 : SampleResourceChild + { + /// + /// Optional. Gets or sets the Id of the resource. + /// + [JsonProperty("properties.name2")] + public string ChildName2 { get; set; } + } + + /// + /// Information for resource. + /// + public partial class SampleResourceWithConflict : IResource + { + /// + /// Gets the ID of the resource. + /// + [JsonProperty("id")] + public string Id { get; private set; } + + /// + /// Gets the name of the resource. + /// + [JsonProperty("name")] + public string Name { get; private set; } + + /// + /// Gets the type of the resource. + /// + [JsonProperty("type")] + public string Type { get; private set; } + + /// + /// Required. Gets or sets the location of the resource. + /// + [JsonProperty("location")] + public string Location { get; set; } + + /// + /// Optional. Gets or sets the tags attached to the resource. + /// + [JsonProperty("tags")] + public IDictionary Tags { get; set; } + + /// + /// Optional. Gets or sets the special location of the resource. + /// + [JsonProperty("properties.location")] + public string SampleResourceWithConflictLocation { get; set; } + + /// + /// Optional. Gets or sets the special id resource. + /// + [JsonProperty("properties.id")] + public string SampleResourceWithConflictId { get; set; } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/USql.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/USql.cs new file mode 100644 index 0000000000000..68ba8c76100a0 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/Sample/USql.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. +// +// Code generated by Microsoft (R) AutoRest Code Generator 0.13.0.0 +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +namespace Microsoft.Azure.Management.DataLake.Analytics.Models +{ + using System; + using System.Linq; + using System.Collections.Generic; + using Newtonsoft.Json; + using Microsoft.Rest; + using Microsoft.Rest.Serialization; + using Microsoft.Rest.Azure; + + /// + /// + public partial class USql : JobProperties + { + /// + /// Initializes a new instance of the USql class. + /// + public USql() { } + + /// + /// Gets or sets the U-SQL algebra file path after the job has + /// completed + /// + [JsonProperty(PropertyName = "algebraFilePath")] + public string AlgebraFilePath { get; set; } + + /// + /// Gets or sets the total time this job spent compiling. This value + /// should not be set by the user and will be ignored if it is. + /// + [JsonProperty(PropertyName = "totalCompilationTime")] + public string TotalCompilationTime { get; set; } + + /// + /// Gets or sets the total time this job spent paused. This value + /// should not be set by the user and will be ignored if it is. + /// + [JsonProperty(PropertyName = "totalPauseTime")] + public string TotalPauseTime { get; set; } + + /// + /// Gets or sets the total time this job spent queued. This value + /// should not be set by the user and will be ignored if it is. + /// + [JsonProperty(PropertyName = "totalQueuedTime")] + public string TotalQueuedTime { get; set; } + + /// + /// Gets or sets the total time this job spent executing. This value + /// should not be set by the user and will be ignored if it is. + /// + [JsonProperty(PropertyName = "totalRunningTime")] + public string TotalRunningTime { get; set; } + + /// + /// Gets or sets the ID used to identify the job manager coordinating + /// job execution. This value should not be set by the user and will + /// be ignored if it is. + /// + [JsonProperty(PropertyName = "rootProcessNodeId")] + public string RootProcessNodeId { get; set; } + + /// + /// Gets or sets the ID used to identify the yarn application + /// executing the job. This value should not be set by the user and + /// will be ignored if it is. + /// + [JsonProperty(PropertyName = "yarnApplicationId")] + public string YarnApplicationId { get; set; } + + /// + /// Gets or sets the timestamp (in ticks) for the yarn application + /// executing the job. This value should not be set by the user and + /// will be ignored if it is. + /// + [JsonProperty(PropertyName = "yarnApplicationTimeStamp")] + public long? YarnApplicationTimeStamp { get; set; } + + /// + /// Gets or sets the compile mode for the job. Possible values for + /// this property include: 'Semantic', 'Full', 'SingleBox'. + /// + [JsonProperty(PropertyName = "compileMode")] + public string CompileMode { get; set; } + + /// + /// Validate the object. Throws ArgumentException or ArgumentNullException if validation fails. + /// + public override void Validate() + { + base.Validate(); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/TokenCloudCredentialsTest.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/TokenCloudCredentialsTest.cs new file mode 100644 index 0000000000000..186fb9623f696 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/TokenCloudCredentialsTest.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net; +using Microsoft.Rest.ClientRuntime.Azure.Test.Fakes; +using Xunit; +using Microsoft.Azure; + +namespace Microsoft.Rest.ClientRuntime.Azure.Test +{ + [Collection("ADAL Test Collection")] + public class TokenCloudCredentialsTest + { + [Fact] + public void TokenCloudCredentialAddsHeader() + { + var tokenCredentials = new TokenCredentials("abc"); + var handler = new RecordedDelegatingHandler { StatusCodeToReturn = HttpStatusCode.OK }; + var fakeClient = new FakeServiceClientWithCredentials(tokenCredentials); + fakeClient = new FakeServiceClientWithCredentials(tokenCredentials, handler); + fakeClient.DoStuff().Wait(); + + Assert.Equal("Bearer", handler.RequestHeaders.Authorization.Scheme); + Assert.Equal("abc", handler.RequestHeaders.Authorization.Parameter); + } + + [Fact] + public void TokenCloudCredentialWithoutSubscriptionAddsHeader() + { + var tokenCredentials = new TokenCredentials("abc"); + var handler = new RecordedDelegatingHandler { StatusCodeToReturn = HttpStatusCode.OK }; + var fakeClient = new FakeServiceClientWithCredentials(tokenCredentials); + fakeClient = new FakeServiceClientWithCredentials(tokenCredentials, handler); + fakeClient.DoStuff().Wait(); + + //Assert.Null(fakeClient.Credentials.SubscriptionId); + Assert.Equal("Bearer", handler.RequestHeaders.Authorization.Scheme); + Assert.Equal("abc", handler.RequestHeaders.Authorization.Parameter); + } + + [Fact] + public void TokenCloudCredentialUpdatesHeader() + { + var credentials = new TokenCredentials("abc"); + var handler = new RecordedDelegatingHandler { StatusCodeToReturn = HttpStatusCode.OK }; + var fakeClient = new FakeServiceClientWithCredentials(credentials); + fakeClient = new FakeServiceClientWithCredentials(credentials, handler); + fakeClient.DoStuff().Wait(); + + Assert.Equal("Bearer", handler.RequestHeaders.Authorization.Scheme); + Assert.Equal("abc", handler.RequestHeaders.Authorization.Parameter); + + credentials= new TokenCredentials("xyz"); + fakeClient = new FakeServiceClientWithCredentials(credentials, handler); + + fakeClient.DoStuff().Wait(); + + Assert.Equal("Bearer", handler.RequestHeaders.Authorization.Scheme); + Assert.Equal("xyz", handler.RequestHeaders.Authorization.Parameter); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/project.json b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/project.json new file mode 100644 index 0000000000000..d04e059a7ed5e --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure.Tests/project.json @@ -0,0 +1,30 @@ +{ + "version": "2.0.0-preview", + "authors": [ "Microsoft" ], + + "packOptions": { + "summary": "ClientRuntime Tests.", + "tags": [ "Microsoft AutoRest ClientRuntime REST" ], + "projectUrl": "https://github.com/Azure/AutoRest", + "licenseUrl": "https://raw.githubusercontent.com/Microsoft/dotnet/master/LICENSE", + }, + + "testRunner": "xunit", + "frameworks": { + "netcoreapp1.0": { + "buildOptions": { "define": [ "PORTABLE" ] }, + "imports": ["dnxcore50", "portable-net45+win8"] + } + }, + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + }, + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.Rest.ClientRuntime.Azure": "[3.3.2,4.0.0)", + "Microsoft.Rest.ClientRuntime.Azure.Authentication": "[2.2.8-preview,3.0.0)", + "xunit": "2.2.0-beta2-build3300", + "dotnet-test-xunit": "2.2.0-preview2-build1029" + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/AzureAsyncOperation.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/AzureAsyncOperation.cs new file mode 100644 index 0000000000000..77d07a4a45935 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/AzureAsyncOperation.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Rest.Azure +{ + /// + /// The response body contains the status of the specified + /// asynchronous operation, indicating whether it has succeeded, is in + /// progress, or has failed. Note that this status is distinct from the + /// HTTP status code returned for the Get Operation Status operation + /// itself. If the asynchronous operation succeeded, the response body + /// includes the HTTP status code for the successful request. If the + /// asynchronous operation failed, the response body includes the HTTP + /// status code for the failed request, and also includes error + /// information regarding the failure. + /// + public class AzureAsyncOperation + { + /// + /// Default delay in seconds for long running operations. + /// + public const int DefaultDelay = 30; + + /// + /// Successful status for long running operations. + /// + public const string SuccessStatus = "Succeeded"; + + /// + /// In progress status for long running operations. + /// + public const string InProgressStatus = "InProgress"; + + /// + /// Failed status for long running operations. + /// + public const string FailedStatus = "Failed"; + + /// + /// Canceled status for long running operations. + /// + public const string CanceledStatus = "Canceled"; + + /// + /// Failed terminal statuses for long running operations. + /// + public static IEnumerable FailedStatuses + { + get { return new[] { FailedStatus, CanceledStatus }; } + } + + /// + /// Terminal statuses for long running operations. + /// + public static IEnumerable TerminalStatuses + { + get { return FailedStatuses.Union(new []{ SuccessStatus }); } + } + + /// + /// The status of the asynchronous request. + /// + public string Status { get; set; } + + /// + /// If the asynchronous operation failed, the response body includes + /// the HTTP status code for the failed request, and also includes + /// error information regarding the failure. + /// + public CloudError Error { get; set; } + + /// + /// Gets or sets the delay in seconds that should be used when checking + /// for the status of the operation. + /// + public int RetryAfter { get; set; } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/AzureClientExtensions.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/AzureClientExtensions.cs new file mode 100644 index 0000000000000..180b9e8adddde --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/AzureClientExtensions.cs @@ -0,0 +1,657 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Rest.ClientRuntime.Azure.Properties; +using Microsoft.Rest.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Rest.Azure +{ + public static class AzureClientExtensions + { + /// + /// Gets operation result for long running operations. + /// + /// Type of the resource body + /// IAzureClient + /// Response from the begin operation + /// Headers that will be added to request + /// Cancellation token + /// Response with created resource + public static async Task> GetLongRunningOperationResultAsync( + this IAzureClient client, + AzureOperationResponse response, + Dictionary> customHeaders, + CancellationToken cancellationToken) where TBody : class + { + if (response == null) + { + throw new ArgumentNullException("response"); + } + var headerlessResponse = new AzureOperationResponse + { + Body = response.Body, + Request = response.Request, + RequestId = response.RequestId, + Response = response.Response + }; + var longRunningResponse = await GetLongRunningOperationResultAsync(client, headerlessResponse, customHeaders, cancellationToken); + return new AzureOperationResponse + { + Body = longRunningResponse.Body, + Request = longRunningResponse.Request, + RequestId = longRunningResponse.RequestId, + Response = longRunningResponse.Response + }; + } + + /// + /// Gets operation result for long running operations. + /// + /// Type of the resource body + /// Type of the resource header + /// IAzureClient + /// Response from the begin operation + /// Headers that will be added to request + /// Cancellation token + /// Response with created resource + public static async Task> GetLongRunningOperationResultAsync( + this IAzureClient client, + AzureOperationResponse response, + Dictionary> customHeaders, + CancellationToken cancellationToken) where TBody : class where THeader : class + { + if (response == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "response"); + } + + if (response.Response == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "response.Response"); + } + + if (response.Request == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "response.Request"); + } + + if (response.Request.Method == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "response.Request.Method"); + } + + var initialRequestMethod = response.Request.Method; + if (CheckResponseStatusCodeFailed(response)) + { + throw new CloudException(string.Format( + Resources.UnexpectedPollingStatus, + response.Response.StatusCode, + initialRequestMethod)); + } + + var pollingState = new PollingState(response, client.LongRunningOperationRetryTimeout); + Uri getOperationUrl = response.Request.RequestUri; + + // Check provisioning state + while (!AzureAsyncOperation.TerminalStatuses.Any(s => s.Equals(pollingState.Status, + StringComparison.OrdinalIgnoreCase))) + { + await Task.Delay(pollingState.DelayInMilliseconds, cancellationToken).ConfigureAwait(false); + + if (!string.IsNullOrEmpty(pollingState.AzureAsyncOperationHeaderLink)) + { + await UpdateStateFromAzureAsyncOperationHeader(client, pollingState, customHeaders, cancellationToken); + } + else if (!string.IsNullOrEmpty(pollingState.LocationHeaderLink)) + { + await UpdateStateFromLocationHeader(client, pollingState, customHeaders, cancellationToken, initialRequestMethod); + } + else if (initialRequestMethod == HttpMethod.Put) + { + await UpdateStateFromGetResourceOperation(client, pollingState, getOperationUrl, + customHeaders, cancellationToken); + } + else + { + throw new CloudException("Location header is missing from long running operation."); + } + } + + if (AzureAsyncOperation.SuccessStatus.Equals(pollingState.Status, StringComparison.OrdinalIgnoreCase)) + { + if ((!string.IsNullOrEmpty(pollingState.AzureAsyncOperationHeaderLink) || pollingState.Resource == null) && + (initialRequestMethod == HttpMethod.Put || initialRequestMethod == new HttpMethod("PATCH"))) + { + await UpdateStateFromGetResourceOperation(client, pollingState, getOperationUrl, customHeaders, + cancellationToken); + } + } + + // Check if operation failed + if (AzureAsyncOperation.FailedStatuses.Any( + s => s.Equals(pollingState.Status, StringComparison.OrdinalIgnoreCase))) + { + throw pollingState.CloudException; + } + + return pollingState.AzureOperationResponse; + } + + /// + /// Gets operation result for long running operations. + /// + /// IAzureClient + /// Response from the begin operation + /// Headers that will be added to request + /// Cancellation token + /// Operation response + public static async Task GetLongRunningOperationResultAsync( + this IAzureClient client, + AzureOperationResponse response, + Dictionary> customHeaders, + CancellationToken cancellationToken) + { + var newResponse = new AzureOperationResponse + { + Request = response.Request, + Response = response.Response, + RequestId = response.RequestId + }; + + var azureOperationResponse = await client.GetLongRunningOperationResultAsync( + newResponse, customHeaders, cancellationToken); + + return new AzureOperationResponse + { + Request = azureOperationResponse.Request, + Response = azureOperationResponse.Response, + RequestId = azureOperationResponse.RequestId + }; + } + + /// + /// Gets operation result for long running operations. + /// + /// Type of the resource headers + /// IAzureClient + /// Response from the begin operation + /// Headers that will be added to request + /// Cancellation token + /// Operation response + public static async Task> GetLongRunningOperationResultAsync( + this IAzureClient client, + AzureOperationHeaderResponse response, + Dictionary> customHeaders, + CancellationToken cancellationToken) where THeader : class + { + if (response == null) + { + throw new ArgumentNullException("response"); + } + var headerlessResponse = new AzureOperationResponse + { + Headers = response.Headers, + Request = response.Request, + RequestId = response.RequestId, + Response = response.Response + }; + var longRunningResponse = await GetLongRunningOperationResultAsync(client, headerlessResponse, customHeaders, cancellationToken); + return new AzureOperationHeaderResponse + { + Headers = longRunningResponse.Headers, + Request = longRunningResponse.Request, + RequestId = longRunningResponse.RequestId, + Response = longRunningResponse.Response + }; + } + + /// + /// Gets operation result for PUT and PATCH operations. (Deprecated, please use GetLongRunningOperationResultAsync) + /// + /// Type of the resource body + /// IAzureClient + /// Response from the begin operation + /// Headers that will be added to request + /// Cancellation token + /// Response with created resource + public static async Task> GetPutOrPatchOperationResultAsync( + this IAzureClient client, + AzureOperationResponse response, + Dictionary> customHeaders, + CancellationToken cancellationToken) where TBody : class + { + return await client.GetLongRunningOperationResultAsync( + response, customHeaders, cancellationToken); + } + + /// + /// Gets operation result for PUT and PATCH operations. (Deprecated, please use GetLongRunningOperationResultAsync) + /// + /// Type of the resource body + /// Type of the resource header + /// IAzureClient + /// Response from the begin operation + /// Headers that will be added to request + /// Cancellation token + /// Response with created resource + public static async Task> GetPutOrPatchOperationResultAsync( + this IAzureClient client, + AzureOperationResponse response, + Dictionary> customHeaders, + CancellationToken cancellationToken) where TBody : class where THeader : class + { + return await GetLongRunningOperationResultAsync(client, response, customHeaders, cancellationToken); + } + + /// + /// Gets operation result for PUT and PATCH operations. (Deprecated, please use GetLongRunningOperationResultAsync) + /// + /// IAzureClient + /// Response from the begin operation + /// Headers that will be added to request + /// Cancellation token + /// Operation response + public static async Task GetPutOrPatchOperationResultAsync( + this IAzureClient client, + AzureOperationResponse response, + Dictionary> customHeaders, + CancellationToken cancellationToken) + { + return await client.GetLongRunningOperationResultAsync( + response, customHeaders, cancellationToken); + } + + /// + /// Gets operation result for DELETE and POST operations. (Deprecated, please use GetLongRunningOperationResultAsync) + /// + /// Type of the resource body + /// IAzureClient + /// Response from the begin operation + /// Headers that will be added to request + /// Cancellation token + /// Operation response + public static async Task> GetPostOrDeleteOperationResultAsync( + this IAzureClient client, + AzureOperationResponse response, + Dictionary> customHeaders, + CancellationToken cancellationToken) where TBody : class + { + return await GetLongRunningOperationResultAsync(client, response, customHeaders, cancellationToken); + } + + /// + /// Gets operation result for DELETE and POST operations. (Deprecated, please use GetLongRunningOperationResultAsync) + /// + /// Type of the resource headers + /// IAzureClient + /// Response from the begin operation + /// Headers that will be added to request + /// Cancellation token + /// Operation response + public static async Task> GetPostOrDeleteOperationResultAsync( + this IAzureClient client, + AzureOperationHeaderResponse response, + Dictionary> customHeaders, + CancellationToken cancellationToken) where THeader : class + { + return await GetLongRunningOperationResultAsync(client, response, customHeaders, cancellationToken); + } + + /// + /// Gets operation result for DELETE and POST operations. (Deprecated, please use GetLongRunningOperationResultAsync) + /// + /// Type of the resource body + /// Type of the resource header + /// IAzureClient + /// Response from the begin operation + /// Headers that will be added to request + /// Cancellation token + /// Operation response + public static async Task> GetPostOrDeleteOperationResultAsync( + this IAzureClient client, + AzureOperationResponse response, + Dictionary> customHeaders, + CancellationToken cancellationToken) where TBody : class where THeader : class + { + return await GetLongRunningOperationResultAsync(client, response, customHeaders, cancellationToken); + } + + /// + /// Gets operation result for DELETE and POST operations. (Deprecated, please use GetLongRunningOperationResultAsync) + /// + /// IAzureClient + /// Response from the begin operation + /// Headers that will be added to request + /// Cancellation token + /// Operation response + public static async Task GetPostOrDeleteOperationResultAsync( + this IAzureClient client, + AzureOperationResponse response, + Dictionary> customHeaders, + CancellationToken cancellationToken) + { + return await client.GetLongRunningOperationResultAsync(response, customHeaders, cancellationToken); + } + + private static bool CheckResponseStatusCodeFailed( + AzureOperationResponse initialResponse) + { + var statusCode = initialResponse.Response.StatusCode; + var method = initialResponse.Request.Method; + if (statusCode == HttpStatusCode.OK || statusCode == HttpStatusCode.Accepted || + (statusCode == HttpStatusCode.Created && method == HttpMethod.Put) || + (statusCode == HttpStatusCode.NoContent && (method == HttpMethod.Delete || method == HttpMethod.Post))) + { + return false; + } + return true; + } + + /// + /// Updates PollingState from GET operations. + /// + /// Type of the resource body. + /// Type of the resource header. + /// IAzureClient + /// Current polling state. + /// Uri for the get operation + /// Headers that will be added to request + /// Cancellation token + /// Task. + private static async Task UpdateStateFromGetResourceOperation( + IAzureClient client, + PollingState pollingState, + Uri getOperationUri, + Dictionary> customHeaders, + CancellationToken cancellationToken) where TBody : class where THeader : class + { + AzureOperationResponse responseWithResource = await GetRawAsync(client, + getOperationUri.AbsoluteUri, customHeaders, cancellationToken).ConfigureAwait(false); + + if (responseWithResource.Body == null) + { + throw new CloudException(Resources.NoBody); + } + + // In 202 pattern on PUT ProvisioningState may not be present in + // the response. In that case the assumption is the status is Succeeded. + var resource = responseWithResource.Body; + if (resource["properties"] != null && resource["properties"]["provisioningState"] != null) + { + pollingState.Status = (string)resource["properties"]["provisioningState"]; + } + else + { + pollingState.Status = AzureAsyncOperation.SuccessStatus; + } + + pollingState.Error = new CloudError() + { + Code = pollingState.Status, + Message = string.Format(Resources.LongRunningOperationFailed, pollingState.Status) + }; + pollingState.Response = responseWithResource.Response; + pollingState.Request = responseWithResource.Request; + pollingState.Resource = responseWithResource.Body.ToObject(JsonSerializer + .Create(client.DeserializationSettings)); + pollingState.ResourceHeaders = responseWithResource.Headers.ToObject(JsonSerializer + .Create(client.DeserializationSettings)); + } + + /// + /// Updates PollingState from Location header. + /// + /// Type of the resource body. + /// Type of the resource header. + /// IAzureClient + /// Current polling state. + /// Headers that will be added to request + /// Cancellation token + /// Http method of the initial long running operation request + /// Task. + private static async Task UpdateStateFromLocationHeader( + IAzureClient client, + PollingState pollingState, + Dictionary> customHeaders, + CancellationToken cancellationToken, + HttpMethod method) where TBody : class where THeader : class + { + AzureOperationResponse responseWithResource = await client.GetRawAsync( + pollingState.LocationHeaderLink, + customHeaders, + cancellationToken).ConfigureAwait(false); + + pollingState.Response = responseWithResource.Response; + pollingState.Request = responseWithResource.Request; + + var statusCode = responseWithResource.Response.StatusCode; + if (statusCode == HttpStatusCode.Accepted) + { + pollingState.Status = AzureAsyncOperation.InProgressStatus; + } + else if (statusCode == HttpStatusCode.OK || + (statusCode == HttpStatusCode.Created && method == HttpMethod.Put) || + (statusCode == HttpStatusCode.NoContent && (method == HttpMethod.Delete || method == HttpMethod.Post))) + { + pollingState.Status = AzureAsyncOperation.SuccessStatus; + + pollingState.Error = new CloudError() + { + Code = pollingState.Status, + Message = string.Format(Resources.LongRunningOperationFailed, pollingState.Status) + }; + pollingState.Resource = responseWithResource.Body == null ? null : responseWithResource.Body.ToObject(JsonSerializer + .Create(client.DeserializationSettings)); + pollingState.ResourceHeaders = responseWithResource.Headers.ToObject(JsonSerializer + .Create(client.DeserializationSettings)); + } + else + { + throw new CloudException("The response from long running operation does not have a valid status code."); + } + } + + /// + /// Updates PollingState from Azure-AsyncOperation header. + /// + /// Type of the resource body. + /// Type of the resource header. + /// IAzureClient + /// Current polling state. + /// Headers that will be added to request + /// Cancellation token + /// Task. + private static async Task UpdateStateFromAzureAsyncOperationHeader( + IAzureClient client, + PollingState pollingState, + Dictionary> customHeaders, + CancellationToken cancellationToken) where TBody : class where THeader : class + { + AzureOperationResponse asyncOperationResponse = + await client.GetAsync( + pollingState.AzureAsyncOperationHeaderLink, + customHeaders, + cancellationToken).ConfigureAwait(false); + + if (asyncOperationResponse.Body == null || asyncOperationResponse.Body.Status == null) + { + throw new CloudException(Resources.NoBody); + } + + pollingState.Status = asyncOperationResponse.Body.Status; + pollingState.Error = asyncOperationResponse.Body.Error; + pollingState.Response = asyncOperationResponse.Response; + pollingState.Request = asyncOperationResponse.Request; + pollingState.Resource = null; + //Try to de-serialize to the response model. (Not required for "PutOrPatch" + //which has the fallback of invoking generic "resource get".) + string responseContent = await pollingState.Response.Content.ReadAsStringAsync(); + var responseHeaders = pollingState.Response.Headers.ToJson(); + try + { + pollingState.Resource = JObject.Parse(responseContent) + .ToObject(JsonSerializer.Create(client.DeserializationSettings)); + pollingState.ResourceHeaders = + responseHeaders.ToObject(JsonSerializer.Create(client.DeserializationSettings)); + } + catch { }; + } + + /// + /// Gets a resource from the specified URL. + /// + /// IAzureClient + /// URL of the resource. + /// Headers that will be added to request + /// Cancellation token + /// + private static async Task> GetAsync( + this IAzureClient client, + string operationUrl, + Dictionary> customHeaders, + CancellationToken cancellationToken) where TBody : class where THeader : class + { + var result = await GetRawAsync(client, operationUrl, customHeaders, cancellationToken); + + TBody body = null; + if (result.Body != null) + { + body = result.Body.ToObject(JsonSerializer.Create(client.DeserializationSettings)); + } + + return new AzureOperationResponse + { + Request = result.Request, + Response = result.Response, + Body = body, + Headers = result.Headers.ToObject(JsonSerializer.Create(client.DeserializationSettings)) + }; + } + + /// + /// Gets a resource from the specified URL. + /// + /// IAzureClient + /// URL of the resource. + /// Headers that will be added to request + /// Cancellation token + /// + private static async Task> GetRawAsync( + this IAzureClient client, + string operationUrl, + Dictionary> customHeaders, + CancellationToken cancellationToken) + { + // Validate + if (operationUrl == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "operationUrl"); + } + + // Tracing + bool shouldTrace = ServiceClientTracing.IsEnabled; + string invocationId = null; + if (shouldTrace) + { + invocationId = ServiceClientTracing.NextInvocationId.ToString(); + var tracingParameters = new Dictionary(); + tracingParameters.Add("operationUrl", operationUrl); + ServiceClientTracing.Enter(invocationId, client, "GetAsync", tracingParameters); + } + + // Construct URL + string url = operationUrl.Replace(" ", "%20"); + + // Create HTTP transport objects + HttpRequestMessage httpRequest = new HttpRequestMessage(); + httpRequest.Method = HttpMethod.Get; + httpRequest.RequestUri = new Uri(url); + + // Set Headers + if (customHeaders != null) + { + foreach (var header in customHeaders) + { + httpRequest.Headers.Add(header.Key, header.Value); + } + } + + // Set Credentials + if (client.Credentials != null) + { + cancellationToken.ThrowIfCancellationRequested(); + await client.Credentials.ProcessHttpRequestAsync(httpRequest, cancellationToken).ConfigureAwait(false); + } + + // Send Request + if (shouldTrace) + { + ServiceClientTracing.SendRequest(invocationId, httpRequest); + } + cancellationToken.ThrowIfCancellationRequested(); + HttpResponseMessage httpResponse = await client.HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); + if (shouldTrace) + { + ServiceClientTracing.ReceiveResponse(invocationId, httpResponse); + } + cancellationToken.ThrowIfCancellationRequested(); + string responseContent = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false); + HttpStatusCode statusCode = httpResponse.StatusCode; + + if (statusCode != HttpStatusCode.OK && + statusCode != HttpStatusCode.Accepted && + statusCode != HttpStatusCode.Created && + statusCode != HttpStatusCode.NoContent) + { + CloudError errorBody = null; + try + { + errorBody = SafeJsonConvert.DeserializeObject(responseContent, client.DeserializationSettings); + } + catch (JsonException) + { + // failed to deserialize, return empty body + } + + throw new CloudException(string.Format(CultureInfo.InvariantCulture, + Resources.LongRunningOperationFailed, statusCode)) + { + Body = errorBody, + Request = new HttpRequestMessageWrapper(httpRequest, null), + Response = new HttpResponseMessageWrapper(httpResponse, responseContent) + }; + } + + JObject body = null; + if (!string.IsNullOrWhiteSpace(responseContent)) + { + try + { + body = JObject.Parse(responseContent); + } + catch + { + // failed to deserialize, return empty body + } + } + + return new AzureOperationResponse + { + Request = httpRequest, + Response = httpResponse, + Body = body, + Headers = httpResponse.Headers.ToJson() + }; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/AzureOperationResponse.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/AzureOperationResponse.cs new file mode 100644 index 0000000000000..e818c00351e1a --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/AzureOperationResponse.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Rest.Azure +{ + /// + /// A standard service response including request ID. + /// + public interface IAzureOperationResponse : IHttpOperationResponse + { + /// + /// Gets or sets the value that uniquely identifies a request + /// made against the service. + /// + string RequestId { get; set; } + } + + /// + /// A standard service response including request ID. + /// + public class AzureOperationResponse : HttpOperationResponse, IAzureOperationResponse + { + /// + /// Gets or sets the value that uniquely identifies a request + /// made against the service. + /// + public string RequestId { get; set; } + } + + /// + /// A standard service response including request ID. + /// + public class AzureOperationResponse : HttpOperationResponse, IAzureOperationResponse + { + /// + /// Gets or sets the value that uniquely identifies a request + /// made against the service. + /// + public string RequestId { get; set; } + } + + public class AzureOperationHeaderResponse : HttpOperationHeaderResponse, IAzureOperationResponse + { + /// + /// Gets or sets the value that uniquely identifies a request + /// made against the service. + /// + public string RequestId { get; set; } + } + + /// + /// A standard service response including request ID. + /// + public class AzureOperationResponse : HttpOperationResponse, IAzureOperationResponse + { + /// + /// Gets or sets the value that uniquely identifies a request + /// made against the service. + /// + public string RequestId { get; set; } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/ClientRequestTrackingHandler.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/ClientRequestTrackingHandler.cs new file mode 100644 index 0000000000000..2edae205600c4 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/ClientRequestTrackingHandler.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Threading; + +namespace Microsoft.Rest.Azure +{ + /// + /// Enables adding a correlation id to messages so that messages that are part of a long-running + /// operation can be grouped together + /// + public class ClientRequestTrackingHandler + : MessageProcessingHandler + { + /// + /// The tracking ID for the operation + /// + public string TrackingId { get; private set; } + + /// + /// Creates a request tracking handler with the specified tracking ID + /// + /// The tracking correlation ID to be added to each http message + public ClientRequestTrackingHandler(string trackingId) + : base() + { + TrackingId = trackingId; + } + + /// + /// Adds the tracking ID for this operation to the outgoing request header + /// + /// The http request message + /// A token that allows canceling the http operation + /// The outgoing http request message with the tracking ID header added + protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, + CancellationToken cancellationToken) + { + if (request == null) + { + throw new ArgumentNullException("request"); + } + request.Headers.Add("client-tracking-id", TrackingId); + return request; + } + + /// + /// Adds the tracking ID for this operation to the incoming response header + /// + /// The http response message + /// A token that allows canceling the http operation + /// The incoming http response message with the tracking ID header added + protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, + CancellationToken cancellationToken) + { + if (response == null) + { + throw new ArgumentNullException("response"); + } + response.Headers.Add("client-tracking-id", TrackingId); + return response; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/CloudError.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/CloudError.cs new file mode 100644 index 0000000000000..3dab455af1f53 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/CloudError.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.Rest.Azure +{ + /// + /// Provides additional information about an http error response + /// + public class CloudError + { + /// + /// Initializes a new instance of CloudError. + /// + public CloudError() + { + Details = new List(); + } + + /// + /// The error code parsed from the body of the http error response + /// + public string Code { get; set; } + + /// + /// The error message parsed from the body of the http error response + /// + public string Message { get; set; } + + /// + /// Gets or sets the target of the error. + /// + public string Target { get; set; } + + /// + /// Gets or sets details for the error. + /// + public IList Details { get; private set; } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/CloudErrorJsonConverter.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/CloudErrorJsonConverter.cs new file mode 100644 index 0000000000000..1a09bd0ba388a --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/CloudErrorJsonConverter.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Rest.Azure +{ + /// + /// JsonConverter that provides custom deserialization for CloudError objects. + /// + public class CloudErrorJsonConverter : JsonConverter + { + private const string ErrorNode = "error"; + + /// + /// Returns true if the object being serialized is a CloudError. + /// + /// The type of the object to check. + /// True if the object being serialized is a CloudError. False otherwise. + public override bool CanConvert(Type objectType) + { + return typeof(CloudError) == objectType; + } + + /// + /// Deserializes an object from a JSON string and flattens out Error property. + /// + /// The JSON reader. + /// The type of the object. + /// The existing value. + /// The JSON serializer. + /// + public override object ReadJson(JsonReader reader, + Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader == null) + { + throw new ArgumentNullException("reader"); + } + if (objectType == null) + { + throw new ArgumentNullException("objectType"); + } + if (serializer == null) + { + throw new ArgumentNullException("serializer"); + } + + JObject jObject = JObject.Load(reader); + JProperty errorObject = jObject.Properties().FirstOrDefault(p => + ErrorNode.Equals(p.Name, StringComparison.OrdinalIgnoreCase)); + if (errorObject != null) + { + jObject = errorObject.Value as JObject; + } + return jObject.ToObject(serializer.WithoutConverter(this)); + } + + /// + /// Serializes an object into a JSON string adding Properties. + /// + /// The JSON writer. + /// The value to serialize. + /// The JSON serializer. + public override void WriteJson(JsonWriter writer, + object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/CloudException.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/CloudException.cs new file mode 100644 index 0000000000000..4eb7db7d48a4d --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/CloudException.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; + +namespace Microsoft.Rest.Azure +{ + /// + /// An exception generated from an http response returned from a Microsoft Azure service + /// + public class CloudException : RestException + { + /// + /// Gets information about the associated HTTP request. + /// + public HttpRequestMessageWrapper Request { get; set; } + + /// + /// Gets information about the associated HTTP response. + /// + public HttpResponseMessageWrapper Response { get; set; } + + /// + /// Gets or sets the response object. + /// + public CloudError Body { get; set; } + + /// + /// Gets or sets the value that uniquely identifies a request + /// made against the service. + /// + public string RequestId { get; set; } + + /// + /// Initializes a new instance of the CloudException class. + /// + public CloudException() : base() + { + } + + /// + /// Initializes a new instance of the CloudException class given exception message. + /// + /// A message describing the error. + public CloudException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the CloudException class caused by another exception. + /// + /// A description of the error. + /// The exception which caused the current exception. + public CloudException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/GlobalSuppressions.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/GlobalSuppressions.cs new file mode 100644 index 0000000000000..a3ef47dc6898a --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/GlobalSuppressions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Rest.Azure.OData", Justification = "OData specific extensions.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Rest.Azure.AzureClientExtensions.#GetPutOrPatchOperationResultAsync`1(Microsoft.Rest.Azure.IAzureClient,Microsoft.Rest.Azure.AzureOperationResponse`1,System.Func`1>>,System.Threading.CancellationToken)", Justification = "Necessary to support service clients.")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#", Scope = "member", Target = "Microsoft.Rest.Azure.AzureClientExtensions.#GetLongRunningOperationStatusAsync(Microsoft.Rest.Azure.IAzureClient,System.String,System.Threading.CancellationToken)", Justification = "Needed to pass URL substring")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Scope = "type", Target = "Microsoft.Rest.Azure.CloudException", Justification = "Exception serialization is not a supported scenario")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Rest.Azure.OData.FilterString.#Generate`1(System.Linq.Expressions.Expression`1>)", Justification = "Using generics to enable intellisense")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "long", Scope = "member", Target = "Microsoft.Rest.Azure.IAzureClient.#LongRunningOperationInitialTimeout", Justification = "LongRunning is a known concept")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "long", Scope = "member", Target = "Microsoft.Rest.Azure.IAzureClient.#LongRunningOperationRetryTimeout", Justification = "LongRunning is a known concept")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Scope = "member", Target = "Microsoft.Rest.Azure.Resource.#Type", Justification = "Model should match the wire representation")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Rest.Azure.Resource.#Tags", Justification = "Need to keep to enable serializing as null or empty collection")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.Rest.Azure.OData.FilterString.#Generate`1(System.Linq.Expressions.Expression`1>)", Justification = "Using generics to enable intellisense")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Scope = "member", Target = "Microsoft.Rest.Azure.OData.UrlExpressionVisitor.#PrintConstant(System.Object)", Justification = "Server expects the values to be lower-cased")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Scope = "member", Target = "Microsoft.Rest.Azure.AzureClientExtensions.#GetPostOrDeleteOperationResultAsync`1(Microsoft.Rest.Azure.IAzureClient,Microsoft.Rest.Azure.AzureOperationResponse`1,System.Threading.CancellationToken)", Justification = "Needed to enable functionality")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Rest.Azure.SubResource.#ProvisioningState", Justification = "Used in deserialization")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Rest.Azure.Resource.#Id")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Rest.Azure.Resource.#Name")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Rest.Azure.Resource.#Type")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Scope = "member", Target = "Microsoft.Rest.Azure.Resource.#ProvisioningState")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Rest.Azure.Authentication")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Scope = "type", Target = "Microsoft.Rest.Azure.IResource", Justification = "Used during as a marker for JSON serialization")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.Rest.Azure.OData.FilterString.#Generate`1(System.Linq.Expressions.Expression`1>,System.Boolean)")] diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/IAzureClient.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/IAzureClient.cs new file mode 100644 index 0000000000000..35bad2fb65b66 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/IAzureClient.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using Newtonsoft.Json; + +namespace Microsoft.Rest.Azure +{ + /// + /// Interface for all Microsoft Azure clients. + /// + public interface IAzureClient + { + /// + /// Gets Azure subscription credentials. + /// + ServiceClientCredentials Credentials { get; } + + /// + /// Gets the HttpClient used for making HTTP requests. + /// + HttpClient HttpClient { get; } + + /// + /// Gets or sets the retry timeout for Long Running Operations. + /// + int? LongRunningOperationRetryTimeout { get; set; } + + /// + /// Gets json serialization settings. + /// + JsonSerializerSettings SerializationSettings { get; } + + /// + /// Gets json deserialization settings. + /// + JsonSerializerSettings DeserializationSettings { get; } + + /// + /// When set to true a unique x-ms-client-request-id value + /// is generated and included in each request. Default is true. + /// + bool? GenerateClientRequestId { get; set; } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/IPage.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/IPage.cs new file mode 100644 index 0000000000000..c8470824dca0f --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/IPage.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace Microsoft.Rest.Azure +{ + /// + /// Defines a page interface in Azure responses. + /// + /// Type of the page content items + public interface IPage : IEnumerable + { + /// + /// Gets the link to the next page. + /// + string NextPageLink { get; } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/IResource.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/IResource.cs new file mode 100644 index 0000000000000..17ae9081d5443 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/IResource.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Rest.Azure +{ + /// + /// Defines Azure resource. + /// + public interface IResource + { + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/JsonSerializerExtensions.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/JsonSerializerExtensions.cs new file mode 100644 index 0000000000000..7a03f00b43e97 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/JsonSerializerExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; + +namespace Microsoft.Rest.Azure +{ + public static class JsonSerializerExtensions + { + /// + /// Gets a JsonSerializer without specified converter. + /// + /// JsonSerializer + /// Converter to exclude from serializer. + /// + public static JsonSerializer WithoutConverter(this JsonSerializer serializer, + JsonConverter converterToExclude) + { + if (serializer == null) + { + throw new ArgumentNullException("serializer"); + } + JsonSerializer newSerializer = new JsonSerializer(); + var properties = typeof(JsonSerializer).GetTypeInfo().DeclaredProperties; + foreach (var property in properties.Where(p => p.SetMethod != null && !p.SetMethod.IsPrivate)) + { + property.SetValue(newSerializer, property.GetValue(serializer, null), null); + } + foreach (var converter in serializer.Converters) + { + if (converter != converterToExclude) + { + newSerializer.Converters.Add(converter); + } + } + return newSerializer; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Microsoft.Rest.ClientRuntime.Azure.sln b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Microsoft.Rest.ClientRuntime.Azure.sln new file mode 100644 index 0000000000000..7ec2aac35c6c4 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Microsoft.Rest.ClientRuntime.Azure.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime", "..\Microsoft.Rest.ClientRuntime\Microsoft.Rest.ClientRuntime.xproj", "{EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Azure", "Microsoft.Rest.ClientRuntime.Azure.xproj", "{D5296EAB-C13E-4A88-9532-BD0677D18EC9}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Azure.Tests", "..\Microsoft.Rest.ClientRuntime.Azure.Tests\Microsoft.Rest.ClientRuntime.Azure.Tests.xproj", "{3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Tests", "..\Microsoft.Rest.ClientRuntime.Tests\Microsoft.Rest.ClientRuntime.Tests.xproj", "{F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Tracing.Tests", "..\Microsoft.Rest.ClientRuntime.Tracing.Tests\Microsoft.Rest.ClientRuntime.Tracing.Tests.xproj", "{52C61F15-BF86-41DC-93D1-05D3DA70F032}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Release|Any CPU.Build.0 = Release|Any CPU + {D5296EAB-C13E-4A88-9532-BD0677D18EC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5296EAB-C13E-4A88-9532-BD0677D18EC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5296EAB-C13E-4A88-9532-BD0677D18EC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5296EAB-C13E-4A88-9532-BD0677D18EC9}.Release|Any CPU.Build.0 = Release|Any CPU + {3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B2346E5-5D1F-4B0A-AEEE-F3AFB9583A72}.Release|Any CPU.Build.0 = Release|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Release|Any CPU.Build.0 = Release|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Microsoft.Rest.ClientRuntime.Azure.xproj b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Microsoft.Rest.ClientRuntime.Azure.xproj new file mode 100644 index 0000000000000..d550943b0cfd0 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Microsoft.Rest.ClientRuntime.Azure.xproj @@ -0,0 +1,23 @@ + + + + ..\..\..\ + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + {d5296eab-c13e-4a88-9532-bd0677d18ec9} + Microsoft.Rest.ClientRuntime.Azure + .\obj + .\bin\ + + + + 2.0 + + + True + + + diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/FilterString.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/FilterString.cs new file mode 100644 index 0000000000000..f08fed614447a --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/FilterString.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace Microsoft.Rest.Azure.OData +{ + /// + /// Handles OData filter generation. + /// + public static class FilterString + { + /// + /// Generates an OData filter from a specified Linq expression. Skips null parameters. + /// + /// Filter type. + /// Entity to use for filter generation. + /// + public static string Generate(Expression> filter) + { + return Generate(filter, true); + } + + /// + /// Generates an OData filter from a specified Linq expression. + /// + /// Filter type. + /// Entity to use for filter generation. + /// Value indicating whether null values should be skipped. + /// + public static string Generate(Expression> filter, bool skipNullFilterParameters) + { + if (filter == null || !filter.Parameters.Any()) + { + return string.Empty; + } + var visitor = new UrlExpressionVisitor(filter.Parameters.First(), skipNullFilterParameters); + visitor.Visit(filter); + return visitor.ToString(); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/ODataMethodAttribute.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/ODataMethodAttribute.cs new file mode 100644 index 0000000000000..22d33bb35b2fe --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/ODataMethodAttribute.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Rest.Azure.OData +{ + /// + /// Annotates OData methods. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public sealed class ODataMethodAttribute : Attribute + { + /// + /// Gets or sets serialized name. + /// + public string MethodName { get; private set; } + + /// + /// Initializes a new instance of ODataMethodAttribute with name. + /// + /// Serialized method name + public ODataMethodAttribute(string methodName) + { + this.MethodName = methodName; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/ODataQuery.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/ODataQuery.cs new file mode 100644 index 0000000000000..252c7739db668 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/ODataQuery.cs @@ -0,0 +1,175 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; + +namespace Microsoft.Rest.Azure.OData +{ + /// + /// Handles OData query generation. + /// + public class ODataQuery + { + /// + /// Initializes a new instance of empty ODataQuery. + /// + public ODataQuery() + { + SkipNullFilterParameters = true; + } + + /// + /// Initializes a new instance of ODataQuery with filter. + /// + /// OData expression. + public ODataQuery(string odataExpression) : this() + { + if (odataExpression != null) + { + var odataElements = odataExpression.Split(new[] {"&"}, StringSplitOptions.RemoveEmptyEntries); + + if (odataElements.Length == 1) + { + // If there is only query expression assume it's filter + if (odataExpression.StartsWith("$filter=", StringComparison.OrdinalIgnoreCase)) + { + Filter = odataExpression.Substring("$filter=".Length); + } + else + { + Filter = odataExpression; + } + } + else + { + // Go through each query expression and parse it + foreach (var element in odataElements) + { + if (element.StartsWith("$filter=", StringComparison.OrdinalIgnoreCase)) + { + Filter = element.Substring("$filter=".Length); + } + else if (element.StartsWith("$orderby=", StringComparison.OrdinalIgnoreCase)) + { + OrderBy = element.Substring("$orderby=".Length); + } + else if (element.StartsWith("$expand=", StringComparison.OrdinalIgnoreCase)) + { + Expand = element.Substring("$expand=".Length); + } + else if (element.StartsWith("$top=", StringComparison.OrdinalIgnoreCase)) + { + int top = 0; + if (int.TryParse(element.Substring("$top=".Length), out top)) + { + Top = top; + } + } + else if (element.StartsWith("$skip=", StringComparison.OrdinalIgnoreCase)) + { + int skip = 0; + if (int.TryParse(element.Substring("$skip=".Length), out skip)) + { + Skip = skip; + } + } + } + } + } + } + + /// + /// Initializes a new instance of ODataQuery with filter. + /// + /// Filter expression. + public ODataQuery(Expression> filter) : this() + { + SetFilter(filter); + } + + /// + /// Gets or sets query $filter expression. + /// + public string Filter { get; set; } + + /// + /// Gets or sets query $orderby expression. + /// + public string OrderBy { get; set; } + + /// + /// Gets or sets query $expand expression. + /// + public string Expand { get; set; } + + /// + /// Gets or sets query $top value. + /// + public int? Top { get; set; } + + /// + /// Gets or sets query $skip value. + /// + public int? Skip { get; set; } + + /// + /// Indicates whether null values in the Filter should be skipped. Default value is True. + /// + public bool SkipNullFilterParameters { get; set; } + + /// + /// Sets Filter from an expression. + /// + /// Filter expression. + public void SetFilter(Expression> filter) + { + Filter = FilterString.Generate(filter, SkipNullFilterParameters); + } + + /// + /// Implicit operator that creates an ODataQuery from a string filter. + /// + /// Filter expression + /// ODataQuery + public static implicit operator ODataQuery(string filter) + { + return new ODataQuery(filter); + } + + public override string ToString() + { + var queryStringList = new List(); + if (!string.IsNullOrEmpty(Filter)) + { + queryStringList.Add(string.Format(CultureInfo.InvariantCulture, + "$filter={0}", Filter)); + } + if (!string.IsNullOrEmpty(OrderBy)) + { + queryStringList.Add(string.Format(CultureInfo.InvariantCulture, + "$orderby={0}", OrderBy)); + } + if (!string.IsNullOrEmpty(Expand)) + { + queryStringList.Add(string.Format(CultureInfo.InvariantCulture, + "$expand={0}", Expand)); + } + if (Top != null) + { + queryStringList.Add(string.Format(CultureInfo.InvariantCulture, + "$top={0}", Top)); + } + if (Skip != null) + { + queryStringList.Add(string.Format(CultureInfo.InvariantCulture, + "$skip={0}", Skip)); + } + + return string.Join("&", queryStringList); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/UrlExpressionVisitor.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/UrlExpressionVisitor.cs new file mode 100644 index 0000000000000..630903a6d551d --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/OData/UrlExpressionVisitor.cs @@ -0,0 +1,484 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Xml; +using Newtonsoft.Json; + +namespace Microsoft.Rest.Azure.OData +{ + /// + /// Expression visitor class that generates OData style $filter parameter. + /// + public class UrlExpressionVisitor : ExpressionVisitor + { + private const string DefaultDateTimeFormat = "yyyy-MM-ddTHH:mm:ssZ"; + private const string PropertiesNode = "properties"; + private readonly Stack _fullExpressionStack = new Stack(); + private StringBuilder _currentExpressionString = new StringBuilder(); + private bool _currentExpressionContainsNull; + private PropertyInfo _currentProperty; + private readonly Expression _baseExpression; + private readonly bool _skipNullFilterParameters; + + /// + /// Initializes a new instance of UrlExpressionVisitor. Skips null parameters. + /// + /// Base expression. + public UrlExpressionVisitor(Expression baseExpression) : this(baseExpression, true) + { } + + /// + /// Initializes a new instance of UrlExpressionVisitor. + /// + /// Base expression. + /// Value indicating whether null values should be skipped. + public UrlExpressionVisitor(Expression baseExpression, bool skipNullFilterParameters) + { + _baseExpression = baseExpression; + _skipNullFilterParameters = skipNullFilterParameters; + + // For binary expression add current string to a stack + // so that if the expression contains null it can be cleared + _fullExpressionStack.Push(_currentExpressionString); + _currentExpressionString = new StringBuilder(); + _currentExpressionContainsNull = false; + } + + /// + /// Visits binary expression (e.g. ==, &&, >, etc). + /// + /// Node to visit. + /// Original node. + protected override Expression VisitBinary(BinaryExpression node) + { + if (node == null) + { + throw new ArgumentNullException("node"); + } + + // For binary expression add current string to a stack + // so that if the expression contains null it can be cleared + _fullExpressionStack.Push(_currentExpressionString); + _currentExpressionString = new StringBuilder(); + _currentExpressionContainsNull = false; + + this.Visit(node.Left); + CloseUnaryBooleanOperator(node.NodeType); + var leftExpressionString = _currentExpressionString; + _currentExpressionString = new StringBuilder(); + + this.Visit(node.Right); + CloseUnaryBooleanOperator(node.NodeType); + var rightExpressionString = _currentExpressionString; + _currentExpressionString = new StringBuilder(); + + if (leftExpressionString.Length > 0) + { + _currentExpressionString.Append(leftExpressionString); + } + if (leftExpressionString.Length > 0 && rightExpressionString.Length > 0) + { + _currentExpressionString.Append(" " + GetODataOperatorName(node.NodeType) + " "); + } + if (rightExpressionString.Length > 0) + { + _currentExpressionString.Append(rightExpressionString); + } + + // Merge expression back into the top expression from the stack + MergeExpressionsWithStack(); + _currentExpressionContainsNull = false; + _currentProperty = null; + + return node; + } + + private void MergeExpressionsWithStack() + { + if (_fullExpressionStack.Count != 0) + { + var lastExpression = _fullExpressionStack.Pop(); + if (!_currentExpressionContainsNull || !_skipNullFilterParameters) + { + lastExpression.Append(_currentExpressionString); + } + _currentExpressionString = lastExpression; + } + } + + /// + /// Visits unary expression (e.g. !foo). + /// + /// Node to visit. + /// Original node. + protected override Expression VisitUnary(UnaryExpression node) + { + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (node.NodeType == ExpressionType.Not) + { + throw new NotSupportedException( + "Unary expressions are not supported. Please use binary expressions (e.g. Property == false) instead."); + } + else + { + return base.VisitUnary(node); + } + } + + /// + /// Visits conditional expression (e.g. foo == true ? bar : fee). Throws NotSupportedException. + /// + /// Node to visit. + /// Throws NotSupportedException. + protected override Expression VisitConditional(ConditionalExpression node) + { + throw new NotSupportedException( + "Conditional sub-expressions are not supported."); + } + + /// + /// Visits new object expression (e.g. new DateTime()). + /// + /// Node to visit. + /// Original node. + protected override Expression VisitNew(NewExpression node) + { + if (node == null) + { + throw new ArgumentNullException("node"); + } + var newObject = node.Constructor.Invoke(node.Arguments.Select(a => ((ConstantExpression)a).Value).ToArray()); + PrintConstant(newObject); + + return node; + } + + /// + /// Visits constants (e.g. 'a' or 123). + /// + /// Node to visit. + /// Original node. + protected override Expression VisitConstant(ConstantExpression node) + { + if (node == null) + { + throw new ArgumentNullException("node"); + } + PrintConstant(node.Value); + return node; + } + + /// + /// Visits object members (e.g. p.Foo or dateTime.Hour). + /// + /// Node to visit. + /// Original node. + protected override Expression VisitMember(MemberExpression node) + { + if (node == null) + { + throw new ArgumentNullException("node"); + } + // Assumes that left side expression parameters are properties like p.Foo + if (ShouldBuildExpression(node)) + { + if (node.Expression.NodeType == ExpressionType.Parameter) + { + var nodeMemberProperty = node.Member as PropertyInfo; + PrintProperty(nodeMemberProperty); + } + else + { + this.Visit(node.Expression); + _currentExpressionString.Append("/"); + + var nodeMemberProperty = node.Member as PropertyInfo; + PrintProperty(nodeMemberProperty); + } + } + else + { + // This fork is executed when right side of the expression is not a constant + + if (!(node.Member is PropertyInfo) && !(node.Member is FieldInfo)) + { + throw new NotSupportedException("Not supported expression: " + node.Member.DeclaringType); + } + PrintConstant(Expression.Lambda(node).Compile().DynamicInvoke()); + } + + return node; + } + + /// + /// Visits object property. + /// + /// Property to print. + private void PrintProperty(PropertyInfo property) + { + if (property != null) + { + _currentProperty = property; + _currentExpressionString.Append(GetPropertyName(property)); + } + else + { + throw new NotSupportedException("Only properties are supported as parameters."); + } + } + + /// + /// Visits method calls including Contains, StartsWith, and EndWith. + /// Methods that are not supported will throw an exception. + /// + /// Node to visit. + /// Original node. + protected override Expression VisitMethodCall(MethodCallExpression node) + { + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (node.Method.Name == "Contains" && + (node.Arguments.Count == 2 || + node.Arguments.Count == 1)) + { + Expression leftSide = node.Object; + Expression rightSide = node.Arguments[0]; + if (node.Arguments.Count == 2) + { + leftSide = node.Arguments[0]; + rightSide = node.Arguments[1]; + } + Visit(leftSide); + _currentExpressionString.Append("/any(c: c eq "); + Visit(rightSide); + _currentExpressionString.Append(")"); + return node; + } + + if (node.Method.Name == "StartsWith" && + (node.Arguments.Count == 2 || + node.Arguments.Count == 1)) + { + Expression leftSide = node.Object; + Expression rightSide = node.Arguments[0]; + if (node.Arguments.Count == 2) + { + leftSide = node.Arguments[0]; + rightSide = node.Arguments[1]; + } + + _currentExpressionString.Append("startswith("); + Visit(leftSide); + _currentExpressionString.Append(","); + Visit(rightSide); + _currentExpressionString.Append(")"); + return node; + } + + if (node.Method.Name == "EndsWith" && + (node.Arguments.Count == 2 || + node.Arguments.Count == 1)) + { + Expression leftSide = node.Object; + Expression rightSide = node.Arguments[0]; + if (node.Arguments.Count == 2) + { + leftSide = node.Arguments[0]; + rightSide = node.Arguments[1]; + } + + _currentExpressionString.Append("endswith("); + Visit(leftSide); + _currentExpressionString.Append(", "); + Visit(rightSide); + _currentExpressionString.Append(")"); + return node; + } + + var methodName = node.Method.Name; + if (node.Method.GetCustomAttributes().Any()) + { + methodName = node.Method.GetCustomAttribute().MethodName; + } + _currentExpressionString.Append(methodName + "("); + for (var i = 0; i < node.Arguments.Count; i++) + { + var argument = node.Arguments[i]; + Visit(argument); + if (i != node.Arguments.Count - 1) + { + _currentExpressionString.Append(", "); + } + } + _currentExpressionString.Append(")"); + return node; + } + + /// + /// Appends 'eq true' to Boolean unary operators. + /// + private void CloseUnaryBooleanOperator(ExpressionType expressionType) + { + if (_currentProperty != null) + { + // Reset unary operator if and/or node type + if (expressionType == ExpressionType.And || expressionType == ExpressionType.AndAlso || + expressionType == ExpressionType.Or || expressionType == ExpressionType.OrElse) + { + if (_currentProperty.PropertyType == typeof (bool)) + { + _currentExpressionString.Append(" eq true"); + } + _currentProperty = null; + } + } + } + + /// + /// Helper method to print constant. + /// + /// Object to print. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] + private void PrintConstant(object val) + { + if (val == null) + { + _currentExpressionString.Append("null"); + _currentExpressionContainsNull = true; + } + else + { + string formattedString; + if (val is DateTime) + { + val = ((DateTime)val).ToUniversalTime(); + formattedString = string.Format(CultureInfo.InvariantCulture, + "{0:" + DefaultDateTimeFormat + "}", val); + } + else if (val is TimeSpan) + { + formattedString = string.Format(CultureInfo.InvariantCulture, + "duration'{0}'", XmlConvert.ToString((TimeSpan)val)); + } + else + { + formattedString = string.Format(CultureInfo.InvariantCulture, + "{0}", val); + } + + if (val is int || + val is bool || + val is long || + val is short) + { + _currentExpressionString.Append(formattedString.ToLowerInvariant()); + } + else if (val is TimeSpan) + { + _currentExpressionString.Append(formattedString); + } + else + { + _currentExpressionString.AppendFormat(CultureInfo.InvariantCulture, "'{0}'", Uri.EscapeDataString(formattedString)); + } + } + } + + /// + /// Helper method to generate property name. + /// + /// Property to examine. + /// Property name or value specified in the FilterParameterAttribute. + private static string GetPropertyName(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + return string.Empty; + } + string propertyName = propertyInfo.Name; + if (propertyInfo.GetCustomAttributes().Any()) + { + propertyName = propertyInfo.GetCustomAttribute().PropertyName; + } + if (typeof(IResource).GetTypeInfo().IsAssignableFrom(propertyInfo.DeclaringType.GetTypeInfo())) + { + propertyName = propertyName.Replace(PropertiesNode + ".", PropertiesNode + "/"); + } + return propertyName; + } + + /// + /// Returns string representation of the current expression. + /// + /// + public override string ToString() + { + CloseUnaryBooleanOperator(ExpressionType.And); + MergeExpressionsWithStack(); + return _currentExpressionString.ToString(); + } + + /// + /// Returns OData representation of the the ExpressionType. + /// + /// Expression type. + /// OData representation of the the ExpressionType. + private static string GetODataOperatorName(ExpressionType exprType) + { + switch (exprType) + { + case ExpressionType.GreaterThan: + return "gt"; + case ExpressionType.GreaterThanOrEqual: + return "ge"; + case ExpressionType.LessThan: + return "lt"; + case ExpressionType.LessThanOrEqual: + return "le"; + case ExpressionType.And: + case ExpressionType.AndAlso: + return "and"; + case ExpressionType.Or: + case ExpressionType.OrElse: + return "or"; + case ExpressionType.Equal: + return "eq"; + case ExpressionType.NotEqual: + return "ne"; + default: + throw new NotSupportedException("Cannot get name for: " + exprType); + } + } + + /// + /// Returns true if base expression matches _baseExpression + /// + /// + /// + private bool ShouldBuildExpression(MemberExpression expression) + { + var parentExpression = expression.Expression as MemberExpression; + if (parentExpression != null) + { + return ShouldBuildExpression(parentExpression); + } + if (expression.Expression == _baseExpression) + { + return true; + } + return false; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/PollingState.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/PollingState.cs new file mode 100644 index 0000000000000..17c38105b0b37 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/PollingState.cs @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http; +using Microsoft.Rest.ClientRuntime.Azure.Properties; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Rest.Azure +{ + /// + /// Defines long running operation polling state. + /// + /// Type of resource body. + /// Type of resource header. + internal class PollingState where TBody : class where THeader : class + { + /// + /// Initializes an instance of PollingState. + /// + /// First operation response. + /// Default timeout. + public PollingState(HttpOperationResponse response, int? retryTimeout) + { + _retryTimeout = retryTimeout; + Response = response.Response; + Request = response.Request; + Resource = response.Body; + ResourceHeaders = response.Headers; + + string raw = response.Response.Content == null ? null : response.Response.Content.AsString(); + + JObject resource = null; + if (!string.IsNullOrEmpty(raw)) + { + try + { + resource = JObject.Parse(raw); + } + catch (JsonException) + { + // failed to deserialize, return empty body + } + } + + switch (Response.StatusCode) + { + case HttpStatusCode.Accepted: + Status = AzureAsyncOperation.InProgressStatus; + break; + case HttpStatusCode.OK: + if (resource != null && resource["properties"] != null && + resource["properties"]["provisioningState"] != null) + { + Status = (string)resource["properties"]["provisioningState"]; + } + else + { + Status = AzureAsyncOperation.SuccessStatus; + } + break; + case HttpStatusCode.Created: + if (resource != null && resource["properties"] != null && + resource["properties"]["provisioningState"] != null) + { + Status = (string) resource["properties"]["provisioningState"]; + } + else + { + Status = AzureAsyncOperation.InProgressStatus; + } + break; + case HttpStatusCode.NoContent: + Status = AzureAsyncOperation.SuccessStatus; + break; + default: + Status = AzureAsyncOperation.FailedStatus; + break; + } + } + + private string _status; + + /// + /// Gets or sets polling status. + /// + public string Status { + get + { + return _status; + } + set + { + if (value == null) + { + throw new CloudException(Resources.NoProvisioningState); + } + _status = value; + } + } + + /// + /// Gets or sets the latest value captured from Azure-AsyncOperation header. + /// + public string AzureAsyncOperationHeaderLink { get; set; } + + /// + /// Gets or sets the latest value captured from Location header. + /// + public string LocationHeaderLink { get; set; } + + private HttpResponseMessage _response; + + /// + /// Gets or sets last operation response. + /// + public HttpResponseMessage Response + { + get { return _response; } + set + { + _response = value; + if (_response != null) + { + if (_response.Headers.Contains("Azure-AsyncOperation")) + { + AzureAsyncOperationHeaderLink = _response.Headers.GetValues("Azure-AsyncOperation").FirstOrDefault(); + } + + if (_response.Headers.Contains("Location")) + { + LocationHeaderLink = _response.Headers.GetValues("Location").FirstOrDefault(); + } + } + } + } + + /// + /// Gets or sets last operation request. + /// + public HttpRequestMessage Request { get; set; } + + /// + /// Gets or sets cloud error. + /// + public CloudError Error { get; set; } + + /// + /// Gets or sets resource. + /// + public TBody Resource { get; set; } + + /// + /// Gets or sets resource header. + /// + public THeader ResourceHeaders { get; set; } + + private int? _retryTimeout; + + /// + /// Gets long running operation delay in milliseconds. + /// + public int DelayInMilliseconds + { + get + { + if (_retryTimeout != null) + { + return _retryTimeout.Value * 1000; + } + if (Response != null && Response.Headers.Contains("Retry-After")) + { + return int.Parse(Response.Headers.GetValues("Retry-After").FirstOrDefault(), + CultureInfo.InvariantCulture) * 1000; + } + return AzureAsyncOperation.DefaultDelay * 1000; + } + } + + /// + /// Gets CloudException from current instance. + /// + public CloudException CloudException + { + get + { + return new CloudException(string.Format(CultureInfo.InvariantCulture, + Resources.LongRunningOperationFailed, Status)) + { + Body = Error, + Request = new HttpRequestMessageWrapper(Request, Request.Content.AsString()), + Response = new HttpResponseMessageWrapper(Response, Response.Content.AsString()) + }; + } + } + + /// + /// Gets AzureOperationResponse from current instance. + /// + public AzureOperationResponse AzureOperationResponse + { + get + { + return new AzureOperationResponse + { + Body = Resource, + Headers = ResourceHeaders, + Request = Request, + Response = Response + }; + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Properties/AssemblyInfo.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000..95eb9c3459801 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Microsoft Rest Azure Client Runtime")] +[assembly: AssemblyDescription("Client infrastructure for Azure client libraries.")] +[assembly: AssemblyVersion("3.0.0.0")] +[assembly: AssemblyFileVersion("3.3.2.0")] + +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft Corporation")] +[assembly: AssemblyProduct("Azure .NET SDK")] +[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] +[assembly: CLSCompliant(false)] +[assembly: ComVisible(false)] diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Properties/Resources.Designer.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Properties/Resources.Designer.cs new file mode 100644 index 0000000000000..04e84d7a82b8e --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Properties/Resources.Designer.cs @@ -0,0 +1,315 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Rest.ClientRuntime.Azure.Properties { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Rest.ClientRuntime.Azure.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Value cannot be empty. + ///Parameter name: {0}. + /// + internal static string ArgumentCannotBeEmpty { + get { + return ResourceManager.GetString("ArgumentCannotBeEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument {0} cannot be greater than its ceiling value of {1}.. + /// + internal static string ArgumentCannotBeGreaterThanBaseline { + get { + return ResourceManager.GetString("ArgumentCannotBeGreaterThanBaseline", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument {0} cannot be initialized with a negative value.. + /// + internal static string ArgumentCannotBeNegative { + get { + return ResourceManager.GetString("ArgumentCannotBeNegative", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Authentication with Azure Active Directory Failed using clientId: {0}. + /// + internal static string AuthenticationValidationFailed { + get { + return ResourceManager.GetString("AuthenticationValidationFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} requires a {1} in its HTTP pipeline to work with client certificates.. + /// + internal static string CertificateCloudCredentials_InitializeServiceClient_NoWebRequestHandler { + get { + return ResourceManager.GetString("CertificateCloudCredentials_InitializeServiceClient_NoWebRequestHandler", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to convert parameter {0} value '{1}' to type {2}.. + /// + internal static string ConfigurationHelper_CreateCouldNotConvertException { + get { + return ResourceManager.GetString("ConfigurationHelper_CreateCouldNotConvertException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {3} Failed to create {0} from connection settings {1} = "{2}".. + /// + internal static string ConfigurationHelper_CreateFromSettings_CreateSettingsFailedException { + get { + return ResourceManager.GetString("ConfigurationHelper_CreateFromSettings_CreateSettingsFailedException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No connection settings found for type {0}. Enable tracing for more information.. + /// + internal static string ConfigurationHelper_CreateFromSettings_NoConnectionSettingsFound { + get { + return ResourceManager.GetString("ConfigurationHelper_CreateFromSettings_NoConnectionSettingsFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No credentials of type '{0}' could be initialized from the provided settings.. + /// + internal static string ConfigurationHelper_GetCredentials_NotFound { + get { + return ResourceManager.GetString("ConfigurationHelper_GetCredentials_NotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parameter {0} is required.. + /// + internal static string ConfigurationHelper_GetParameter_NotFound { + get { + return ResourceManager.GetString("ConfigurationHelper_GetParameter_NotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to convert parameter {0} value '{1}' to type {2}.. + /// + internal static string CouldNotConvertToCertificateType { + get { + return ResourceManager.GetString("CouldNotConvertToCertificateType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Default retry strategy for technology {0}, named '{1}', is not defined.. + /// + internal static string DefaultRetryStrategyMappingNotFound { + get { + return ResourceManager.GetString("DefaultRetryStrategyMappingNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Default retry strategy for technology {0} was not not defined, and there is no overall default strategy.. + /// + internal static string DefaultRetryStrategyNotFound { + get { + return ResourceManager.GetString("DefaultRetryStrategyNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Authentication error while configuring active directory: '{0}'.. + /// + internal static string ErrorCreatingAuthenticationContext { + get { + return ResourceManager.GetString("ErrorCreatingAuthenticationContext", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Retry handler is not present in the HttpClient handler stack.. + /// + internal static string ExceptionRetryHandlerMissing { + get { + return ResourceManager.GetString("ExceptionRetryHandlerMissing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The RetryManager is already set.. + /// + internal static string ExceptionRetryManagerAlreadySet { + get { + return ResourceManager.GetString("ExceptionRetryManagerAlreadySet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The default RetryManager has not been set. Set it by invoking the RetryManager.SetDefault static method, or if you are using declarative configuration, you can invoke the RetryPolicyFactory.CreateDefault() method to automatically create the retry manager from the configuration file.. + /// + internal static string ExceptionRetryManagerNotSet { + get { + return ResourceManager.GetString("ExceptionRetryManagerNotSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Long running operation failed with status '{0}'.. + /// + internal static string LongRunningOperationFailed { + get { + return ResourceManager.GetString("LongRunningOperationFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The response from long running operation does not contain a body.. + /// + internal static string NoBody { + get { + return ResourceManager.GetString("NoBody", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Location header is missing from long running operation.. + /// + internal static string NoHeader { + get { + return ResourceManager.GetString("NoHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Provisioning state is missing from long running operation.. + /// + internal static string NoProvisioningState { + get { + return ResourceManager.GetString("NoProvisioningState", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Response status code indicates server error: {0} ({1}).. + /// + internal static string ResponseStatusCodeError { + get { + return ResourceManager.GetString("ResponseStatusCodeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The action has exceeded its defined retry limit.. + /// + internal static string RetryLimitExceeded { + get { + return ResourceManager.GetString("RetryLimitExceeded", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The retry strategy with name '{0}' cannot be found.. + /// + internal static string RetryStrategyNotFound { + get { + return ResourceManager.GetString("RetryStrategyNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified string argument {0} must not be empty.. + /// + internal static string StringCannotBeEmpty { + get { + return ResourceManager.GetString("StringCannotBeEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument '{0}' cannot return a null task when invoked.. + /// + internal static string TaskCannotBeNull { + get { + return ResourceManager.GetString("TaskCannotBeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument '{0}' must return a scheduled task (also known as "hot" task) when invoked.. + /// + internal static string TaskMustBeScheduled { + get { + return ResourceManager.GetString("TaskMustBeScheduled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unexpected polling status code from long running operation '{0}' for method '{1}'.. + /// + internal static string UnexpectedPollingStatus { + get { + return ResourceManager.GetString("UnexpectedPollingStatus", resourceCulture); + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Properties/Resources.resx b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Properties/Resources.resx new file mode 100644 index 0000000000000..8fb340da09bbb --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Properties/Resources.resx @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Value cannot be empty. +Parameter name: {0} + + + The specified argument {0} cannot be greater than its ceiling value of {1}. + + + The specified argument {0} cannot be initialized with a negative value. + + + Authentication with Azure Active Directory Failed using clientId: {0} + + + {0} requires a {1} in its HTTP pipeline to work with client certificates. + + + Failed to convert parameter {0} value '{1}' to type {2}. + + + {3} Failed to create {0} from connection settings {1} = "{2}". + + + No connection settings found for type {0}. Enable tracing for more information. + + + No credentials of type '{0}' could be initialized from the provided settings. + + + Parameter {0} is required. + + + Failed to convert parameter {0} value '{1}' to type {2}. + + + Default retry strategy for technology {0}, named '{1}', is not defined. + + + Default retry strategy for technology {0} was not not defined, and there is no overall default strategy. + + + Authentication error while configuring active directory: '{0}'. + + + Retry handler is not present in the HttpClient handler stack. + + + The RetryManager is already set. + + + The default RetryManager has not been set. Set it by invoking the RetryManager.SetDefault static method, or if you are using declarative configuration, you can invoke the RetryPolicyFactory.CreateDefault() method to automatically create the retry manager from the configuration file. + + + Long running operation failed with status '{0}'. + + + The response from long running operation does not contain a body. + + + Location header is missing from long running operation. + + + Provisioning state is missing from long running operation. + + + Response status code indicates server error: {0} ({1}). + + + The action has exceeded its defined retry limit. + + + The retry strategy with name '{0}' cannot be found. + + + The specified string argument {0} must not be empty. + + + The specified argument '{0}' cannot return a null task when invoked. + + + The specified argument '{0}' must return a scheduled task (also known as "hot" task) when invoked. + + + Unexpected polling status code from long running operation '{0}' for method '{1}'. + + \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/ResourceJsonConverter.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/ResourceJsonConverter.cs new file mode 100644 index 0000000000000..c808836af9819 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/ResourceJsonConverter.cs @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Microsoft.Rest.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Rest.Azure +{ + /// + /// JsonConverter that provides custom serialization for resource-based objects. + /// + public class ResourceJsonConverter : JsonConverter + { + private const string PropertiesNode = "properties"; + + /// + /// Returns true if the object being serialized is assignable from the Resource type. False otherwise. + /// + /// The type of the object to check. + /// True if the object being serialized is assignable from the base type. False otherwise. + public override bool CanConvert(Type objectType) + { + return typeof(IResource).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); + } + + /// + /// Deserializes an object from a JSON string and flattens out Properties. + /// + /// The JSON reader. + /// The type of the object. + /// The existing value. + /// The JSON serializer. + /// + public override object ReadJson(JsonReader reader, + Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader == null) + { + throw new ArgumentNullException("reader"); + } + if (objectType == null) + { + throw new ArgumentNullException("objectType"); + } + if (serializer == null) + { + throw new ArgumentNullException("serializer"); + } + + try + { + JObject resourceJObject = JObject.Load(reader); + // Flatten resource + JObject propertiesJObject = resourceJObject[PropertiesNode] as JObject; + + // Update type if there is a polymorphism + var polymorphicDeserializer = serializer.Converters + .FirstOrDefault(c => + c.GetType().GetTypeInfo().IsGenericType && + c.GetType().GetGenericTypeDefinition() == typeof(PolymorphicDeserializeJsonConverter<>) && + c.CanConvert(objectType)) as PolymorphicJsonConverter; + if (polymorphicDeserializer != null) + { + objectType = PolymorphicJsonConverter.GetDerivedType(objectType, + (string) resourceJObject[polymorphicDeserializer.Discriminator]) ?? objectType; + } + + // Initialize appropriate type instance + var resource = Activator.CreateInstance(objectType); + + // For each property in resource - populate property + var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType); + foreach (JsonProperty property in contract.Properties) + { + JToken propertyValueToken = resourceJObject[property.PropertyName]; + if (propertyValueToken == null && + propertiesJObject != null && + property.PropertyName.StartsWith("properties.", StringComparison.OrdinalIgnoreCase)) + { + propertyValueToken = propertiesJObject[property.PropertyName.Substring("properties.".Length)]; + } + + if (propertyValueToken != null && property.Writable) + { + var propertyValue = propertyValueToken.ToObject(property.PropertyType, serializer); + property.ValueProvider.SetValue(resource, propertyValue); + } + } + return resource; + } + catch (JsonException) + { + return null; + } + } + + /// + /// Serializes an object into a JSON string adding Properties. + /// + /// The JSON writer. + /// The value to serialize. + /// The JSON serializer. + public override void WriteJson(JsonWriter writer, + object value, JsonSerializer serializer) + { + if (writer == null) + { + throw new ArgumentNullException("writer"); + } + if (value == null) + { + throw new ArgumentNullException("value"); + } + if (serializer == null) + { + throw new ArgumentNullException("serializer"); + } + + // If generic resource - serialize as-is + if (value.GetType().GetTypeInfo().DeclaredProperties.Any(p => p.Name == "Properties" && p.PropertyType == typeof(object))) + { + GetSerializerWithoutCurrentConverter(serializer).Serialize(writer, value); + return; + } + + // Add discriminator field + writer.WriteStartObject(); + + // If there is polymorphism - add polymorphic property + var polymorphicSerializer = serializer.Converters + .FirstOrDefault(c => + c.GetType().GetTypeInfo().IsGenericType && + c.GetType().GetGenericTypeDefinition() == typeof(PolymorphicSerializeJsonConverter<>) && + c.CanConvert(value.GetType())) as PolymorphicJsonConverter; + + if (polymorphicSerializer != null) + { + writer.WritePropertyName(polymorphicSerializer.Discriminator); + string typeName = value.GetType().Name; + if (value.GetType().GetTypeInfo().GetCustomAttributes().Any()) + { + typeName = value.GetType().GetTypeInfo().GetCustomAttribute().Id; + } + writer.WriteValue(typeName); + } + + // Go over each property that is in resource and write to stream + JsonConverterHelper.SerializeProperties(writer, value, serializer, + p => !p.PropertyName.StartsWith("properties.", StringComparison.OrdinalIgnoreCase)); + + // If there is a need to add properties element - add it + var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType()); + if (contract.Properties.Any(p => + p.PropertyName.StartsWith("properties.", StringComparison.OrdinalIgnoreCase) && + (p.ValueProvider.GetValue(value) != null || + serializer.NullValueHandling == NullValueHandling.Include))) + { + writer.WritePropertyName(PropertiesNode); + writer.WriteStartObject(); + JsonConverterHelper.SerializeProperties(writer, value, serializer, + p => p.PropertyName.StartsWith("properties.", StringComparison.OrdinalIgnoreCase)); + writer.WriteEndObject(); + } + + writer.WriteEndObject(); + } + + /// + /// Gets a JsonSerializer without current converter. + /// + /// JsonSerializer + /// + protected JsonSerializer GetSerializerWithoutCurrentConverter(JsonSerializer serializer) + { + if (serializer == null) + { + throw new ArgumentNullException("serializer"); + } + JsonSerializer newSerializer = new JsonSerializer(); + var properties = typeof(JsonSerializer).GetTypeInfo().DeclaredProperties; + foreach (var property in properties.Where(p => p.SetMethod != null && !p.SetMethod.IsPrivate)) + { + property.SetValue(newSerializer, property.GetValue(serializer, null), null); + } + foreach (var converter in serializer.Converters) + { + if (converter != this) + { + newSerializer.Converters.Add(converter); + } + } + return newSerializer; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Settings.SourceAnalysis b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Settings.SourceAnalysis new file mode 100644 index 0000000000000..2e1b08cea6461 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/Settings.SourceAnalysis @@ -0,0 +1,196 @@ + + + NoMerge + + dq + dq-lit + eq + esc-dq + esc-eq + esc-sq + sq + sq-lit + ws + + + + + + + + True + + + + + True + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + Microsoft + [###LICENSE_NAME###] + True + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + + as + do + id + if + in + is + my + no + on + to + ui + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/project.json b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/project.json new file mode 100644 index 0000000000000..4f7b2234f2c39 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Azure/project.json @@ -0,0 +1,83 @@ +{ + "version": "3.3.2", + "copyright": "Copyright (c) Microsoft Corporation", + "title": "Client Runtime for Microsoft Azure Libraries", + "description": "Provides common error handling, tracing, and HTTP/REST-based pipeline manipulation. \nSupported Platforms:\n - Portable Class Libraries\n - .NET Framework 4.5\n - Windows 8\n - Windows Phone 8.1\n - DotNet Core", + "authors": [ "Microsoft" ], + + "packOptions": { + "summary": "Client infrastructure for Azure client libraries.", + "iconUrl": "http://go.microsoft.com/fwlink/?LinkID=288890", + "projectUrl": "https://github.com/Azure/AutoRest", + "licenseUrl": "https://raw.githubusercontent.com/Microsoft/dotnet/master/LICENSE", + "tags": [ "Microsoft Azure AutoRest ClientRuntime REST azureofficial" ], + "requireLicenseAcceptance": true + }, + + "buildOptions": { + "delaySign": true, + "publicSign": false, + "keyFile": "../../../tools/MSSharedLibKey.snk" + }, + + "dependencies": { + "Microsoft.Rest.ClientRuntime": "[2.3.2,3.0)" + }, + + "frameworks": { + "net45": { + "frameworkAssemblies": { + "mscorlib": "", + "System": "", + "System.Net": "", + "System.Net.Http": "", + "System.Net.Http.WebRequest": "", + "System.Xml": "" + } + }, + "netstandard1.1": { + "buildOptions": { "define": [ "PORTABLE" ] }, + "imports": ["dnxcore50", "portable-net45+win8"], + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.Diagnostics.Tracing": "4.1.0", + "System.Globalization": "4.0.11", + "System.Linq": "4.1.0", + "System.Net.Http": "4.1.0", + "System.Net.Primitives": "4.0.11", + "System.ObjectModel": "4.0.12", + "System.Reflection.Extensions": "4.0.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime.Extensions": "4.1.0", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11" + } + }, + "netstandard1.5": { + "buildOptions": { "define": [ "PORTABLE" ] }, + "imports": ["dnxcore50"], + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.Diagnostics.Tracing": "4.1.0", + "System.Globalization": "4.0.11", + "System.Linq": "4.1.0", + "System.Net.Http": "4.1.0", + "System.Net.Primitives": "4.0.11", + "System.ObjectModel": "4.0.12", + "System.Reflection.Extensions": "4.0.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime.Extensions": "4.1.0", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11" + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/EtwTracingInterceptor.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/EtwTracingInterceptor.cs new file mode 100644 index 0000000000000..06efbce0c6715 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/EtwTracingInterceptor.cs @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace Microsoft.Rest.Tracing.Etw +{ + /// + /// Implementation for IServiceClientTracingInterceptor that raises ETW events. + /// + /// + /// To use ETW with the Microsoft AutoRest generated client: + /// 1. Register the logger. + /// 2. Use tools such as PerfView to capture events under the + /// Microsoft.Rest provider. + /// + /// + /// This shows how to hook up the tracing interceptor. + /// + /// TracingAdapter.AddTracingInterceptor( + /// new EtwTracingInterceptor()); + /// + /// + public class EtwTracingInterceptor : IServiceClientTracingInterceptor + { + /// + /// Trace information. + /// + /// The information to trace. + public void Information(string message) + { + HttpOperationEventSource.Log.Information(message); + } + + /// + /// Probe configuration for the value of a setting. + /// + /// The configuration source. + /// The name of the setting. + /// The value of the setting in the source. + public void Configuration(string source, string name, string value) + { + HttpOperationEventSource.Log.Configuration(source, name, value); + } + + /// + /// Enter a method. + /// + /// Method invocation identifier. + /// The instance with the method. + /// Name of the method. + /// Method parameters. + public void EnterMethod(string invocationId, object instance, string method, + IDictionary parameters) + { + string instanceAsString = instance == null ? string.Empty : instance.ToString(); + string parametersAsString = parameters == null ? string.Empty : parameters.AsFormattedString(); + + HttpOperationEventSource.Log.Enter(invocationId, instanceAsString, method, parametersAsString); + } + + /// + /// Send an HTTP request. + /// + /// Method invocation identifier. + /// The request about to be sent. + public virtual void SendRequest(string invocationId, HttpRequestMessage request) + { + string requestAsString = request == null ? string.Empty : request.AsFormattedString(); + + HttpOperationEventSource.Log.SendRequest(invocationId, requestAsString); + } + + /// + /// Receive an HTTP response. + /// + /// Method invocation identifier. + /// The response instance. + public virtual void ReceiveResponse(string invocationId, HttpResponseMessage response) + { + string responseAsString = response == null ? string.Empty : response.AsFormattedString(); + + HttpOperationEventSource.Log.ReceiveResponse(invocationId, responseAsString); + } + + /// + /// Raise an error. + /// + /// Method invocation identifier. + /// The error. + public void TraceError(string invocationId, Exception exception) + { + string exceptionAsString = exception == null ? string.Empty : exception.ToString(); + + HttpOperationEventSource.Log.Error(invocationId, exceptionAsString); + } + + /// + /// Exit a method. Note: Exit will not be called in the event of an + /// error. + /// + /// Method invocation identifier. + /// Method return value. + public void ExitMethod(string invocationId, object returnValue) + { + string returnValueAsString = returnValue == null ? string.Empty : returnValue.ToString(); + + HttpOperationEventSource.Log.Exit(invocationId, returnValueAsString); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/HttpOperationEventSource.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/HttpOperationEventSource.cs new file mode 100644 index 0000000000000..05bd3ae615bf5 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/HttpOperationEventSource.cs @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Diagnostics.Tracing; + +namespace Microsoft.Rest.Tracing.Etw +{ + /// + /// Class that inherits from EventSource and is used as a data model for ETW events. + /// + [EventSource(Name = "Microsoft.Rest")] + internal sealed class HttpOperationEventSource : EventSource + { + private static HttpOperationEventSource _log; + + private HttpOperationEventSource() + { + } + + /// + /// Gets an instance of the HttpOperationEventSource. + /// + public static HttpOperationEventSource Log + { + get + { + if (_log == null) + { + _log = new HttpOperationEventSource(); + } + return _log; + } + } + + /// + /// Logs information message. + /// + /// Message + [Event(1, Level = EventLevel.Informational)] + public void Information(string Message) + { + WriteEvent(1, Message); + } + + /// + /// Logs a configuration event. + /// + /// Event source. + /// Configuration name. + /// Configuration value. + [Event(2, Level = EventLevel.Informational)] + public void Configuration(string Source, string Name, string Value) + { + WriteEvent(2, Source, Name, Value); + } + + /// + /// Logs method start. + /// + /// Correlation ID for a series of events. + /// Instance of the method. + /// Method name. + /// Method parameters passed to the method. + [Event(3, Level = EventLevel.Informational)] + public void Enter(string InvocationId, string Instance, string Method, string Parameters) + { + WriteEvent(3, InvocationId, Instance, Method, Parameters); + } + + /// + /// Logs sending an HTTP request. + /// + /// Correlation ID for a series of events. + /// The request about to be sent. + [Event(4, Level = EventLevel.Informational)] + public void SendRequest(string InvocationId, string Request) + { + WriteEvent(4, InvocationId, Request); + } + + /// + /// Logs receipt of an HTTP response. + /// + /// Correlation ID for a series of events. + /// The response instance. + [Event(5, Level = EventLevel.Informational)] + public void ReceiveResponse(string InvocationId, string Response) + { + WriteEvent(5, InvocationId, Response); + } + + /// + /// Logs an error. + /// + /// Correlation ID for a series of events. + /// Exception. + [Event(6, Level = EventLevel.Error)] + public void Error(string InvocationId, string Exception) + { + WriteEvent(6, InvocationId, Exception); + } + + /// + /// Logs method exit. + /// + /// Correlation ID for a series of events. + /// Return value. + [Event(7, Level = EventLevel.Informational)] + public void Exit(string InvocationId, string ReturnValue) + { + WriteEvent(7, InvocationId, ReturnValue); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/Microsoft.Rest.ClientRuntime.Etw.xproj b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/Microsoft.Rest.ClientRuntime.Etw.xproj new file mode 100644 index 0000000000000..ccae6127f7e25 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/Microsoft.Rest.ClientRuntime.Etw.xproj @@ -0,0 +1,23 @@ + + + + ..\..\..\ + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 218d7297-8254-4c70-9c04-33c3d5ee9709 + Microsoft.Rest.ClientRuntime + .\obj + .\bin\ + + + + 2.0 + + + True + + + diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/Properties/AssemblyInfo.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000..69ee47abee456 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Microsoft Rest Client Runtime ETW Logger")] +[assembly: AssemblyDescription("Provides ETW tracing of \"Microsoft.Rest\" Client Library events.")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.1.1.0")] + +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft Corporation")] +[assembly: AssemblyProduct("Microsoft AutoRest")] +[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/README.md b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/README.md new file mode 100644 index 0000000000000..a118b30e34e35 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/README.md @@ -0,0 +1,11 @@ +# ETW Logger for Microsoft AutoRest Generated Clients + +Exposes AutoRest Generated Libraries events via ETW (Event Tracing for Windows). ETW events can be captured by subscribing to Microsoft.Rest event source. Requires .NET Framework 4.5 or newer. + +# Getting started + +1. Register the logger by having this line called at the start of the application +```csharp +ServiceClientInterceptor.AddTracingInterceptor(new EtwTracingInterceptor()); +``` +2. Use a tool such as [PerfView](http://www.microsoft.com/en-us/download/details.aspx?id=28567) to capture events under the ```Microsoft.Rest``` provider. diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/project.json b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/project.json new file mode 100644 index 0000000000000..f7349915981af --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Etw/project.json @@ -0,0 +1,34 @@ +{ + "version": "2.1.1-preview", + "title": "ETW Logger for Microsoft AutoRest Generated Clients", + "description": "Enables tracing of event messages for AutoRest generated client libraries events via ETW (Event Tracing for Windows). ETW events can be captured by subscribing to the \"Microsoft.Rest\" event source.", + "copyright": "Copyright (c) Microsoft Corporation", + "authors": [ "Microsoft" ], + + "packOptions": { + "summary": "Provides ETW tracing of \"Microsoft.Rest\" Client Library events.", + "iconUrl": "https://cdn.rawgit.com/Azure/AutoRest/7c1576dfb56974176223545cfac5762d168ded5f/Documentation/images/autorest-small-flat.png", + "projectUrl": "https://github.com/Azure/AutoRest", + "licenseUrl": "https://raw.githubusercontent.com/Microsoft/dotnet/master/LICENSE", + "tags": [ "Microsoft AutoRest ClientRuntime REST ETW" ], + "requireLicenseAcceptance": true, + }, + + "buildOptions": { + "delaySign": true, + "publicSign": false, + "keyFile": "../../../tools/MSSharedLibKey.snk" + }, + + "dependencies": { + "Microsoft.Rest.ClientRuntime": "[2.3.2,3.0)" + }, + + "frameworks": { + "net45": { + "frameworkAssemblies": { + "System": "" + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/Log4NetTracingInterceptor.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/Log4NetTracingInterceptor.cs new file mode 100644 index 0000000000000..5cb49f6eb60a7 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/Log4NetTracingInterceptor.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net.Http; +using log4net; + +namespace Microsoft.Rest.Tracing.Log4Net +{ + /// + /// Implementation for IServiceClientTracingInterceptor that works using log4net framework. + /// + public class Log4NetTracingInterceptor : IServiceClientTracingInterceptor + { + private ILog _logger; + + /// + /// Initializes a new instance of the class with log4net logger. + /// + /// log4net logger. + public Log4NetTracingInterceptor(ILog logger) + { + _logger = logger; + } + + /// + /// Initializes a new instance of the class with configuration file. + /// + /// The configuration file absolute path. + public Log4NetTracingInterceptor(string filePath) + : this(LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)) + { + if (!string.IsNullOrEmpty(filePath)) + { + if (File.Exists(filePath)) + { + log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(filePath)); + } + else + { + throw new FileNotFoundException(filePath); + } + } + } + + /// + /// Initializes a new instance of the class without configuration file. + /// + public Log4NetTracingInterceptor() : this(string.Empty) + { + } + + /// + /// Trace information. + /// + /// The information to trace. + public void Information(string message) + { + _logger.Info(message); + } + + /// + /// Probe configuration for the value of a setting. + /// + /// The configuration source. + /// The name of the setting. + /// The value of the setting in the source. + public void Configuration(string source, string name, string value) + { + _logger.DebugFormat(CultureInfo.InvariantCulture, + "Configuration: source={0}, name={1}, value={2}", source, name, value); + } + + /// + /// Enter a method. + /// + /// Method invocation identifier. + /// The instance with the method. + /// Name of the method. + /// Method parameters. + public void EnterMethod(string invocationId, object instance, string method, + IDictionary parameters) + { + _logger.DebugFormat(CultureInfo.InvariantCulture, + "invocationId: {0}\r\ninstance: {1}\r\nmethod: {2}\r\nparameters: {3}", + invocationId, instance, method, parameters.AsFormattedString()); + } + + /// + /// Send an HTTP request. + /// + /// Method invocation identifier. + /// The request about to be sent. + public void SendRequest(string invocationId, HttpRequestMessage request) + { + string requestAsString = (request == null ? string.Empty : request.AsFormattedString()); + _logger.DebugFormat(CultureInfo.InvariantCulture, + "invocationId: {0}\r\nrequest: {1}", invocationId, requestAsString); + } + + /// + /// Receive an HTTP response. + /// + /// Method invocation identifier. + /// The response instance. + public void ReceiveResponse(string invocationId, HttpResponseMessage response) + { + string requestAsString = (response == null ? string.Empty : response.AsFormattedString()); + _logger.DebugFormat(CultureInfo.InvariantCulture, + "invocationId: {0}\r\nresponse: {1}", invocationId, requestAsString); + } + + /// + /// Raise an error. + /// + /// Method invocation identifier. + /// The error. + public void TraceError(string invocationId, Exception exception) + { + _logger.Error("invocationId: " + invocationId, exception); + } + + /// + /// Exit a method. Note: Exit will not be called in the event of an + /// error. + /// + /// Method invocation identifier. + /// Method return value. + public void ExitMethod(string invocationId, object returnValue) + { + string returnValueAsString = (returnValue == null ? string.Empty : returnValue.ToString()); + _logger.DebugFormat(CultureInfo.InvariantCulture, + "Exit with invocation id {0}, the return value is {1}", + invocationId, + returnValueAsString); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/Microsoft.Rest.ClientRuntime.Log4Net.xproj b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/Microsoft.Rest.ClientRuntime.Log4Net.xproj new file mode 100644 index 0000000000000..bdf387befc061 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/Microsoft.Rest.ClientRuntime.Log4Net.xproj @@ -0,0 +1,23 @@ + + + + ..\..\..\ + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 348e414f-101a-4939-99ff-2c994a965a89 + Microsoft.Rest.ClientRuntime + .\obj + .\bin\ + + + + 2.0 + + + True + + + diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/Properties/AssemblyInfo.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000..f0a53dff95af0 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Microsoft Rest Client Runtime Log4Net Logger")] +[assembly: AssemblyDescription("Provides Log4Net logging of \"Microsoft.Rest\" Client Library events.")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.1.1.0")] + +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft Corporation")] +[assembly: AssemblyProduct("Microsoft AutoRest")] +[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/README.md b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/README.md new file mode 100644 index 0000000000000..b7183550d27bb --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/README.md @@ -0,0 +1,42 @@ +Using Log4Net for AutoRest Generated Clients: +--------------------------------------------- +1- Configure the log4net in your app.config/web.config (or your preferred way). + For more examples on the available configurations check [config examples](http://logging.apache.org/log4net/release/config-examples.html) + Here's an example of app.config for the logger used with ConsoleAppender: + + + + + +
+ + + + + + + + + + + + + + + + + + + +2- Configure log4net in the application that is using a generated client library. This can be done by + A) Adding this line to ```AssemblyInfo.cs``` of the application: +```csharp +[assembly: log4net.Config.XmlConfigurator(Watch = true, ConfigFile = "FileName.ext")] +``` + B) Passing the config file name to ```Log4NetTracingInterceptor``` constructor. + +3- Last step is to register the logger into the ServiceClientTracing and enable tracing by having these lines called at the start of the application: +```csharp + ServiceClientTracing.AddTracingInterceptor(new Log4NetTracingInterceptor()); + ServiceClientTracing.IsEnabled = true; +``` \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/project.json b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/project.json new file mode 100644 index 0000000000000..37831d1e5f278 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Log4Net/project.json @@ -0,0 +1,38 @@ +{ + "version": "2.1.2-preview", + "title": "Log4Net Logger for Microsoft AutoRest Generated Clients", + "description": "Enables logging of event messages for AutoRest generated client libraries via Log4Net.", + "copyright": "Copyright (c) Microsoft Corporation", + "authors": [ "Microsoft" ], + + "packOptions": { + "summary": "Provides Log4Net logging for AutoRest generated client libraries.", + "iconUrl": "https://cdn.rawgit.com/Azure/AutoRest/7c1576dfb56974176223545cfac5762d168ded5f/Documentation/images/autorest-small-flat.png", + "projectUrl": "https://github.com/Azure/AutoRest", + "licenseUrl": "https://raw.githubusercontent.com/Microsoft/dotnet/master/LICENSE", + "tags": [ "Microsoft AutoRest ClientRuntime REST ETW" ], + "requireLicenseAcceptance": true, + }, + + "buildOptions": { + "delaySign": true, + "keyFile": "../../../tools/MSSharedLibKey.snk" + }, + + "dependencies": { + "Microsoft.Rest.ClientRuntime": "[2.3.2,3.0)", + "log4net": "2.0.3" + }, + + "frameworks": { + "net45": { + "frameworkAssemblies": { + "System": "", + "System.Net": "", + "System.Net.Http": "", + "System.Net.Http.WebRequest": "", + "System.Xml": "" + } + } + }, +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/AddHeaderResponseDelegatingHandler.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/AddHeaderResponseDelegatingHandler.cs new file mode 100644 index 0000000000000..c63d297b0d09b --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/AddHeaderResponseDelegatingHandler.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Threading.Tasks; + +namespace Microsoft.Rest.ClientRuntime.Tests.Fakes +{ + public class AddHeaderResponseDelegatingHandler : DelegatingHandler + { + public AddHeaderResponseDelegatingHandler(string headerName, string headerValue) + { + HeaderName = headerName; + HeaderValue = headerValue; + } + + public string HeaderName { get; set; } + public string HeaderValue { get; set; } + + protected override async Task SendAsync(HttpRequestMessage request, + System.Threading.CancellationToken cancellationToken) + { + HttpResponseMessage response = await base.SendAsync(request, cancellationToken); + response.Headers.Add(HeaderName, HeaderValue); + return response; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/AppenderDelegatingHandler.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/AppenderDelegatingHandler.cs new file mode 100644 index 0000000000000..5b3aa1bf996ff --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/AppenderDelegatingHandler.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Threading.Tasks; + +namespace Microsoft.Rest.ClientRuntime.Tests.Fakes +{ + public class AppenderDelegatingHandler : DelegatingHandler + { + public string Value { get; set; } + + public AppenderDelegatingHandler(string value) + { + Value = value; + } + + protected override async Task SendAsync(HttpRequestMessage request, + System.Threading.CancellationToken cancellationToken) + { + var content = await request.Content.ReadAsStringAsync(); + content += "+" + Value; + StringContent newContent = new StringContent(content); + request.Content = newContent; + HttpResponseMessage response = await base.SendAsync(request, cancellationToken); + return response; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/BadResponseDelegatingHandler.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/BadResponseDelegatingHandler.cs new file mode 100644 index 0000000000000..29a06d79a1184 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/BadResponseDelegatingHandler.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Rest.ClientRuntime.Tests.Fakes +{ + public class BadResponseDelegatingHandler : DelegatingHandler + { + public BadResponseDelegatingHandler() + { + StatusCodeToReturn = HttpStatusCode.InternalServerError; + NumberOfTimesToFail = int.MaxValue; + } + + public int NumberOfTimesFailedSoFar { get; private set; } + + public int NumberOfTimesToFail { get; set; } + + public HttpStatusCode StatusCodeToReturn { get; set; } + + protected override Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK); + if (NumberOfTimesToFail > NumberOfTimesFailedSoFar) + { + response = new HttpResponseMessage(StatusCodeToReturn); + NumberOfTimesFailedSoFar++; + } + return Task.Run(() => response); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/FakeHttpHandler.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/FakeHttpHandler.cs new file mode 100644 index 0000000000000..494e97c5ab34c --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/FakeHttpHandler.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Rest.ClientRuntime.Tests.Fakes +{ + public class FakeHttpHandler : HttpClientHandler + { + public FakeHttpHandler() + { + StatusCodeToReturn = HttpStatusCode.InternalServerError; + NumberOfTimesToFail = int.MaxValue; + } + + public int NumberOfTimesToFail { get; set; } + + public int NumberOfTimesFailedSoFar { get; private set; } + + public HttpStatusCode StatusCodeToReturn { get; set; } + + protected override Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + var response = new HttpResponseMessage(HttpStatusCode.OK); + if (NumberOfTimesToFail > NumberOfTimesFailedSoFar) + { + response = new HttpResponseMessage(StatusCodeToReturn); + NumberOfTimesFailedSoFar++; + } + + return Task.Run(() => response); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/FakeServiceClient.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/FakeServiceClient.cs new file mode 100644 index 0000000000000..b745a789087af --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/FakeServiceClient.cs @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Rest.ClientRuntime.Tests.Fakes +{ + public class FakeServiceClient : ServiceClient + { + private ServiceClientCredentials _clientCredentials; + private HttpRequestMessage _httpRequest; + + public FakeServiceClient() + { + // Prevent base constructor from executing + } + + public FakeServiceClient(HttpClientHandler httpMessageHandler, params DelegatingHandler[] handlers) + : base(httpMessageHandler, handlers) + { + } + + public FakeServiceClient( + HttpClientHandler httpMessageHandler, + ServiceClientCredentials credentials, + params DelegatingHandler[] handlers) + : this(httpMessageHandler, handlers) + { + _clientCredentials = credentials; + } + + + private async Task DoStuff(string content = null) + { + // Construct URL + string url = "http://tempuri.norg"; + + // Create HTTP transport objects + _httpRequest = null; + + _httpRequest = new HttpRequestMessage(); + _httpRequest.Method = HttpMethod.Get; + _httpRequest.RequestUri = new Uri(url); + + // Set content + if (content != null) + { + _httpRequest.Content = new StringContent(content); + } + + // Set Headers + _httpRequest.Headers.Add("x-ms-version", "2013-11-01"); + + // Set Credentials + var cancellationToken = new CancellationToken(); + if (_clientCredentials != null) + { + await _clientCredentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + } + cancellationToken.ThrowIfCancellationRequested(); + return await this.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + } + + private async Task DoStuffAndThrow(string content = null) + { + // Construct URL + string url = "http://tempuri.norg"; + + // Create HTTP transport objects + _httpRequest = null; + + _httpRequest = new HttpRequestMessage(); + _httpRequest.Method = HttpMethod.Get; + _httpRequest.RequestUri = new Uri(url); + + // Set content + if (content != null) + { + _httpRequest.Content = new StringContent(content); + } + + // Set Headers + _httpRequest.Headers.Add("x-ms-version", "2013-11-01"); + + // Set Credentials + var cancellationToken = new CancellationToken(); + if (_clientCredentials != null) + { + await _clientCredentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + } + cancellationToken.ThrowIfCancellationRequested(); + var httpResponse = await this.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false); + throw new HttpOperationException + { + Request = new HttpRequestMessageWrapper(_httpRequest, content), + Response = new HttpResponseMessageWrapper(httpResponse, httpResponse.Content.AsString()) + }; + } + + public HttpResponseMessage DoStuffSync(string content = null) + { + return Task.Factory.StartNew(() => + { + return DoStuff(content); + }).Unwrap().GetAwaiter().GetResult(); + } + + public HttpOperationResponse DoStuffAndThrowSync(string content = null) + { + return Task.Factory.StartNew(() => + { + return DoStuffAndThrow(content); + }).Unwrap().GetAwaiter().GetResult(); + } + + protected override void Dispose(bool disposing) + { + _httpRequest.Dispose(); + base.Dispose(disposing); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/FakeServiceClientWithCredentials.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/FakeServiceClientWithCredentials.cs new file mode 100644 index 0000000000000..e88ad27297220 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/FakeServiceClientWithCredentials.cs @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Rest.ClientRuntime.Tests.Fakes +{ + public class FakeServiceClientWithCredentials : ServiceClient + { + private Uri _baseUri; + private ServiceClientCredentials _credentials; + + /// + /// Initializes a new instance of the FakeServiceClientWithCredentials class. + /// + private FakeServiceClientWithCredentials() : base() + { + } + + /// + /// Initializes a new instance of the FakeServiceClientWithCredentials class. + /// + public FakeServiceClientWithCredentials(ServiceClientCredentials credentials, Uri baseUri, + params DelegatingHandler[] handlers) + : base(handlers) + { + if (credentials == null) + { + throw new ArgumentNullException("credentials"); + } + if (baseUri == null) + { + throw new ArgumentNullException("baseUri"); + } + + InitializeClient(credentials, baseUri); + } + + public FakeServiceClientWithCredentials(ServiceClientCredentials credentials, Uri baseUri, + HttpClientHandler rootHandler, params DelegatingHandler[] handlers) : base(rootHandler, handlers) + { + if (credentials == null) + { + throw new ArgumentNullException("credentials"); + } + if (baseUri == null) + { + throw new ArgumentNullException("baseUri"); + } + + InitializeClient(credentials, baseUri); + } + + /// + /// Initializes a new instance of the FakeServiceClientWithCredentials class. + /// + public FakeServiceClientWithCredentials(ServiceClientCredentials credentials, + params DelegatingHandler[] handlers) + : base(handlers) + { + if (credentials == null) + { + throw new ArgumentNullException("credentials"); + } + + InitializeClient(credentials, new Uri("https://TBD")); + } + + public FakeServiceClientWithCredentials(ServiceClientCredentials credentials, HttpClientHandler rootHandler, + params DelegatingHandler[] handlers) + : base(rootHandler, handlers) + { + if (credentials == null) + { + throw new ArgumentNullException("credentials"); + } + + InitializeClient(credentials, new Uri("https://TBD")); + } + + public Uri BaseUri + { + get { return _baseUri; } + } + + public ServiceClientCredentials Credentials + { + get { return _credentials; } + } + + public async Task DoStuff() + { + // Construct URL + string url = "http://www.microsoft.com"; + + // Create HTTP transport objects + HttpRequestMessage httpRequest = null; + + httpRequest = new HttpRequestMessage(); + httpRequest.Method = HttpMethod.Get; + httpRequest.RequestUri = new Uri(url); + + await this.Credentials.ProcessHttpRequestAsync(httpRequest, new CancellationToken()); + + // Set Headers + httpRequest.Headers.Add("x-ms-version", "2013-11-01"); + + // Set Credentials + var cancellationToken = new CancellationToken(); + cancellationToken.ThrowIfCancellationRequested(); + return await HttpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false); + } + + protected void InitializeClient(ServiceClientCredentials credentials, Uri baseUri) + { + this._credentials = credentials; + this._baseUri = baseUri; + this.Credentials.InitializeServiceClient(this); + HttpClient.Timeout = TimeSpan.FromSeconds(300); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/MirrorDelegatingHandler.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/MirrorDelegatingHandler.cs new file mode 100644 index 0000000000000..65db2941c56ef --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/MirrorDelegatingHandler.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Microsoft.Rest.ClientRuntime.Tests.Fakes +{ + public class MirrorDelegatingHandler : DelegatingHandler + { + protected override async Task SendAsync(HttpRequestMessage request, + System.Threading.CancellationToken cancellationToken) + { + HttpStatusCode responseCode = HttpStatusCode.OK; + IEnumerable headerValues = null; + if (request.Headers.TryGetValues("response-code", out headerValues)) + { + Enum.TryParse(headerValues.First(), out responseCode); + } + + var requestContent = await request.Content.ReadAsStringAsync(); + var response = new HttpResponseMessage(responseCode); + response.Content = new StringContent(requestContent); + return response; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/MirrorMessageHandler.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/MirrorMessageHandler.cs new file mode 100644 index 0000000000000..9369eeb3420e3 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/MirrorMessageHandler.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Microsoft.Rest.ClientRuntime.Tests.Fakes +{ + public class MirrorMessageHandler : HttpMessageHandler + { + public string Value { get; set; } + + public MirrorMessageHandler(string value) + { + Value = value; + } + + protected override async Task SendAsync(HttpRequestMessage request, + System.Threading.CancellationToken cancellationToken) + { + HttpStatusCode responseCode = HttpStatusCode.OK; + IEnumerable headerValues = null; + if (request.Headers.TryGetValues("response-code", out headerValues)) + { + Enum.TryParse(headerValues.First(), out responseCode); + } + + var requestContent = await request.Content.ReadAsStringAsync(); + requestContent += "+" + Value; + var response = new HttpResponseMessage(responseCode); + response.Content = new StringContent(requestContent); + return response; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/RecordedDelegatingHandler.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/RecordedDelegatingHandler.cs new file mode 100644 index 0000000000000..833d027b50b2b --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Fakes/RecordedDelegatingHandler.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; + +namespace Microsoft.Rest.ClientRuntime.Tests.Fakes +{ + public class RecordedDelegatingHandler : DelegatingHandler + { + private readonly HttpResponseMessage _response; + + public RecordedDelegatingHandler() + { + StatusCodeToReturn = HttpStatusCode.Created; + } + + public RecordedDelegatingHandler(HttpResponseMessage response) + { + StatusCodeToReturn = HttpStatusCode.Created; + _response = response; + } + + public HttpContentHeaders ContentHeaders { get; private set; } + + public bool IsPassThrough { get; set; } + + public HttpMethod Method { get; private set; } + + public string Request { get; private set; } + + public HttpRequestHeaders RequestHeaders { get; private set; } + + public HttpStatusCode StatusCodeToReturn { get; set; } + + public Uri Uri { get; private set; } + + protected override async Task SendAsync(HttpRequestMessage request, + System.Threading.CancellationToken cancellationToken) + { + // Save request + if (request.Content == null) + { + Request = string.Empty; + } + else + { + Request = await request.Content.ReadAsStringAsync(); + } + RequestHeaders = request.Headers; + if (request.Content != null) + { + ContentHeaders = request.Content.Headers; + } + Method = request.Method; + Uri = request.RequestUri; + + // Prepare response + if (IsPassThrough) + { + return await base.SendAsync(request, cancellationToken); + } + + if (_response != null) + { + return _response; + } + else + { + var response = new HttpResponseMessage(StatusCodeToReturn); + response.Content = new StringContent(""); + return response; + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/JsonSerializationTests.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/JsonSerializationTests.cs new file mode 100644 index 0000000000000..c08bfa3866e93 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/JsonSerializationTests.cs @@ -0,0 +1,456 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Globalization; +using System.Net.Http; +using Microsoft.Rest.ClientRuntime.Tests.Resources; +using Microsoft.Rest.Serialization; +using Microsoft.Rest.TransientFaultHandling; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using Xunit; +using System.Collections.Generic; + +namespace Microsoft.Rest.ClientRuntime.Tests +{ + [Collection("Serialization Tests")] + public class JsonSerializationTests + { + private const string JsonErrorMessage = "Inappropriate use of JsonConvert.DefaultSettings detected!"; + + [Fact] + public void PolymorphicSerializeWorks() + { + Zoo zoo = new Zoo() { Id = 1 }; + zoo.Animals.Add(new Dog() { Name = "Fido", LikesDogfood = true }); + zoo.Animals.Add(new Cat() { Name = "Felix", LikesMice = false, Dislikes = new Dog() { Name = "Angry", LikesDogfood = true } }); + var serializeSettings = new JsonSerializerSettings(); + serializeSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + + var deserializeSettings = new JsonSerializerSettings(); + deserializeSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + + var serializedJson = JsonConvert.SerializeObject(zoo, Formatting.Indented, serializeSettings); + var zoo2 = JsonConvert.DeserializeObject(serializedJson, deserializeSettings); + + Assert.Equal(zoo.Animals[0].GetType(), zoo2.Animals[0].GetType()); + Assert.Equal(zoo.Animals[1].GetType(), zoo2.Animals[1].GetType()); + Assert.Equal(((Cat)zoo.Animals[1]).Dislikes.GetType(), ((Cat)zoo2.Animals[1]).Dislikes.GetType()); + Assert.Contains("dType", serializedJson); + } + + [Fact] + public void PolymorphismWorksWithReadOnlyProperties() + { + var deserializeSettings = new JsonSerializerSettings(); + deserializeSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + deserializeSettings.NullValueHandling = NullValueHandling.Ignore; + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + + string zooWithPrivateSet = @"{ + ""Id"": 1, + ""Animals"": [ + { + ""dType"": ""dog"", + ""likesDogfood"": true, + ""name"": ""Fido"" + }, + { + ""dType"": ""cat"", + ""likesMice"": false, + ""dislikes"": { + ""dType"": ""dog"", + ""likesDogfood"": true, + ""name"": ""Angry"" + }, + ""name"": ""Felix"" + }, + { + ""dType"": ""siamese"", + ""color"": ""grey"", + ""likesMice"": false, + ""name"": ""Felix"" + } + ] +}"; + + var zoo2 = JsonConvert.DeserializeObject(zooWithPrivateSet, deserializeSettings); + + Assert.Equal("grey", ((Siamese)zoo2.Animals[2]).Color); + + var serializeSettings = new JsonSerializerSettings(); + serializeSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + serializeSettings.NullValueHandling = NullValueHandling.Ignore; + serializeSettings.Formatting = Formatting.Indented; + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + var zooReserialized = JsonConvert.SerializeObject(zoo2, serializeSettings); + + Assert.Equal(zooWithPrivateSet, zooReserialized); + } + + [Fact] + public void RawJsonIsSerialized() + { + var serializeSettings = new JsonSerializerSettings(); + serializeSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + serializeSettings.ContractResolver = new ReadOnlyJsonContractResolver(); + + var firstAlienJson = JsonConvert.SerializeObject(new Alien("green") { Name = "autorest", Planet = "Mars", Body = JObject.Parse(@"{ ""custom"" : ""json"" }") }, + Formatting.Indented, serializeSettings); + + var firstAlien = JsonConvert.DeserializeObject(firstAlienJson, serializeSettings); + + string secondAlienJson = @"{ + ""color"": ""green"", + ""planet"": ""Mars"", + ""name"": ""autorest"", + ""body"": { ""custom"" : ""json"" }, + }"; + + var secondAlien = JsonConvert.DeserializeObject(secondAlienJson, serializeSettings); + + Assert.Equal("autorest", firstAlien.Name); + Assert.Null(firstAlien.Color); + Assert.Null(firstAlien.GetPlanetName()); + Assert.Equal("json", firstAlien.Body.custom.ToString()); + + Assert.Equal("autorest", secondAlien.Name); + Assert.Equal("green", secondAlien.Color); + Assert.Equal("json", secondAlien.Body.custom.ToString()); + } + + [Fact] + public void ReadOnlyPropertiesWorkStandalone() + { + var serializeSettings = new JsonSerializerSettings(); + serializeSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + serializeSettings.ContractResolver = new ReadOnlyJsonContractResolver(); + + var firstAlienJson = JsonConvert.SerializeObject(new Alien("green") { Name = "autorest", Planet = "Mars" }, + Formatting.Indented, serializeSettings); + + var firstAlien = JsonConvert.DeserializeObject(firstAlienJson, serializeSettings); + + string secondAlienJson = @"{ + ""color"": ""green"", + ""planet"": ""Mars"", + ""name"": ""autorest"" + }"; + + var secondAlien = JsonConvert.DeserializeObject(secondAlienJson, serializeSettings); + + Assert.Equal("autorest", firstAlien.Name); + Assert.Null(firstAlien.Color); + Assert.Null(firstAlien.GetPlanetName()); + + Assert.Equal("autorest", secondAlien.Name); + Assert.Equal("green", secondAlien.Color); + Assert.Equal("Mars", secondAlien.GetPlanetName()); + } + + [Fact] + public void DateSerializationWithoutNulls() + { + var localDateTimeOffset = new DateTimeOffset(2015, 6, 1, 16, 10, 08, 121, new TimeSpan(-7, 0, 0)); + var utcDate = DateTime.Parse("2015-06-01T00:00:00.0", CultureInfo.InvariantCulture); + var serializeSettings = new JsonSerializerSettings(); + serializeSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + serializeSettings.Formatting = Formatting.Indented; + serializeSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat; + serializeSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; + + DateTestObject test = new DateTestObject(); + test.Date = localDateTimeOffset.LocalDateTime; + test.DateNullable = localDateTimeOffset.LocalDateTime; + test.DateTime = localDateTimeOffset.LocalDateTime; + test.DateTimeNullable = localDateTimeOffset.LocalDateTime; + test.DateTimeOffset = localDateTimeOffset; + test.DateTimeOffsetNullable = localDateTimeOffset; + test.DateTimeOffsetWithConverter = localDateTimeOffset; + test.DateTimeOffsetNullableWithConverter = localDateTimeOffset; + + var expectedJson = @"{ + ""d"": ""2015-06-01"", + ""dt"": ""2015-06-01T23:10:08.121Z"", + ""dn"": ""2015-06-01T23:10:08.121Z"", + ""dtn"": ""2015-06-01"", + ""dtoc"": ""2015-06-01"", + ""dtonc"": ""2015-06-01"", + ""dto"": ""2015-06-01T16:10:08.121-07:00"", + ""dton"": ""2015-06-01T16:10:08.121-07:00"" +}"; + var json = JsonConvert.SerializeObject(test, serializeSettings); + + DateTestObject testRoundtrip = JsonConvert.DeserializeObject(json, serializeSettings); + Assert.Equal(expectedJson, json); + Assert.Equal(utcDate, testRoundtrip.Date); + Assert.Equal(localDateTimeOffset, testRoundtrip.DateTime.ToLocalTime()); + Assert.Equal(test.DateTimeOffset, testRoundtrip.DateTimeOffset); + } + + [Fact] + public void DateSerializationWithNulls() + { + var serializeSettings = new JsonSerializerSettings(); + serializeSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + serializeSettings.Formatting = Formatting.Indented; + serializeSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat; + serializeSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; + serializeSettings.NullValueHandling = NullValueHandling.Ignore; + + DateTestObject test = new DateTestObject(); + + var expectedJson = @"{ + ""d"": ""0001-01-01"", + ""dt"": ""0001-01-01T00:00:00Z"", + ""dtoc"": ""0001-01-01"", + ""dto"": ""0001-01-01T00:00:00+00:00"" +}"; + var json = JsonConvert.SerializeObject(test, serializeSettings); + + DateTestObject testRoundtrip = JsonConvert.DeserializeObject(json, serializeSettings); + + Assert.Equal(expectedJson, json); + Assert.Null(testRoundtrip.DateNullable); + Assert.Null(testRoundtrip.DateTimeNullable); + } + + [Fact] + public void DateSerializationWithMaxValue() + { + var localDateTime = DateTime.Parse("9999-12-31T22:59:59-01:00", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal).ToLocalTime(); + var serializeSettings = new JsonSerializerSettings(); + serializeSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + serializeSettings.Formatting = Formatting.Indented; + serializeSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat; + serializeSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; + + DateTestObject test = new DateTestObject(); + test.Date = localDateTime; + test.DateNullable = localDateTime; + test.DateTime = localDateTime; + test.DateTimeNullable = localDateTime; + test.DateTimeOffsetNullable = localDateTime; + test.DateTimeOffsetNullableWithConverter = localDateTime; + test.DateTimeOffsetWithConverter = localDateTime; + + var expectedJson = @"{ + ""d"": ""9999-12-31"", + ""dt"": ""9999-12-31T23:59:59Z"", + ""dn"": ""9999-12-31T23:59:59Z"", + ""dtn"": ""9999-12-31"", + ""dtoc"": ""9999-12-31"", + ""dtonc"": ""9999-12-31"", + ""dto"": ""0001-01-01T00:00:00+00:00"", + ""dton"": """ + localDateTime.ToString("yyyy-MM-ddTHH:mm:sszzz") + @""" +}"; + var json = JsonConvert.SerializeObject(test, serializeSettings); + + DateTestObject testRoundtrip = JsonConvert.DeserializeObject(json, serializeSettings); + Assert.Equal(localDateTime, testRoundtrip.DateTime.ToLocalTime()); + Assert.Equal(expectedJson, json); + } + + [Fact] + public void HeaderGetsSerializedToJson() + { + var message = new HttpResponseMessage(); + message.Headers.Add("h1", "value"); + message.Headers.Add("h2", ""); + message.Headers.Add("h3", new string[] {"value1", "value2"}); + var json = message.Headers.ToJson().ToString(); + var expectedJsonString = string.Format("{{{0} \"h1\": \"value\",{0} \"h2\": \"\",{0} \"h3\": [{0} \"value1\",{0} \"value2\"{0} ]{0}}}", + Environment.NewLine); + Assert.Equal(expectedJsonString, json); + } + + [Fact] + public void HeaderKnownValuesGetSerializedToJson() + { + var message = new HttpResponseMessage(); + message.Content = new StringContent("Test"); + message.Headers.Add("h1", "value"); + message.Headers.Add("h2", ""); + message.Headers.Add("h3", new string[] { "value1", "value2" }); + + var json = message.GetHeadersAsJson().ToString(); + + var expectedJsonString = string.Format("{{{0} \"h1\": \"value\",{0} \"h2\": \"\",{0} \"h3\": [{0} \"value1\",{0} \"value2\"{0} ],{0} \"Content-Type\": \"text/plain; charset=utf-8\"{0}}}", + Environment.NewLine); + Assert.Equal(expectedJsonString, json); + } + + [Fact] + public void SafeSerializeIgnoresDefaultSettings() + { + TestWithBadJsonSerializerSettings(() => + { + const string ExpectedJson = @"{""Name"":""Bob"",""Rating"":5}"; + var model = new Model() { Name = "Bob", Rating = 5 }; + + string actualJson = SafeJsonConvert.SerializeObject(model, new JsonSerializerSettings()); + + Assert.Equal(ExpectedJson, actualJson); + }); + } + + [Fact] + public void SafeDeserializeIgnoresDefaultSettings() + { + TestWithBadJsonSerializerSettings(() => + { + const string Json = @"{""Name"":""Bob"",""Rating"":5}"; + + Model model = SafeJsonConvert.DeserializeObject(Json, new JsonSerializerSettings()); + + Assert.Equal("Bob", model.Name); + Assert.Equal(5, model.Rating); + }); + } + + [Fact] + public void SerializeConstants() + { + var settings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + DateFormatHandling = DateFormatHandling.IsoDateFormat, + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver(), + Converters = new List + { + new Iso8601TimeSpanConverter() + } + }; + var json = SafeJsonConvert.SerializeObject(new ModelWithConst(), settings); + Assert.Contains("foo", json); + } + + [Fact] + public void PolymorphicDeserializationConsidersOnlyOwnDerivedTypes() + { + var deserializeSettings = new JsonSerializerSettings(); + deserializeSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + deserializeSettings.NullValueHandling = NullValueHandling.Ignore; + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + + string zooWithPrivateSetHalf1 = @"{ + ""Id"": 1, + ""Animals"": [ + { + ""dType"": ""dog"", + ""likesDogfood"": true, + ""name"": ""Fido"" + }, + { + ""dType"": ""cat"", + ""likesMice"": false, + ""dislikes"": { + ""dType"": ""dog"", + ""likesDogfood"": true, + ""name"": ""Angry"" + }, + ""name"": ""Felix"" + }, + { + ""dType"": ""siamese"", + ""color"": ""grey"", + ""likesMice"": false, + ""name"": ""Felix"" + } + ]"; + string zooWithPrivateSetHalf2 = @" +}"; + string alienPart = @", + ""Alien"" : [ + { + ""dType"": ""siamese"", + ""name"": ""martian"" + } + ]"; + + string zooWithPrivateSet = zooWithPrivateSetHalf1 + alienPart + zooWithPrivateSetHalf2; + + var zoo2 = JsonConvert.DeserializeObject(zooWithPrivateSet, deserializeSettings); + + Assert.Equal("grey", ((Siamese)zoo2.Animals[2]).Color); + + var serializeSettings = new JsonSerializerSettings(); + serializeSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize; + serializeSettings.NullValueHandling = NullValueHandling.Ignore; + serializeSettings.Formatting = Formatting.Indented; + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + var zooReserialized = JsonConvert.SerializeObject(zoo2, serializeSettings); + + Assert.Equal(zooReserialized, zooWithPrivateSetHalf1 + zooWithPrivateSetHalf2); + } + + private static void TestWithBadJsonSerializerSettings(Action callback) + { + Func oldDefault = JsonConvert.DefaultSettings; + JsonConvert.DefaultSettings = () => + new JsonSerializerSettings() + { + Converters = new[] { new InvalidJsonConverter() }, + ContractResolver = new InvalidContractResolver() + }; + + try + { + callback(); + } + finally + { + JsonConvert.DefaultSettings = oldDefault; + } + } + + private class Model + { + public string Name { get; set; } + + public int Rating { get; set; } + } + + private class ModelWithConst + { + [JsonProperty("name", DefaultValueHandling = DefaultValueHandling.Include)] + public static string Name { get { return "foo"; } } + + public int Rating { get; set; } + } + + private class InvalidContractResolver : IContractResolver + { + public JsonContract ResolveContract(Type type) + { + throw new InvalidOperationException(JsonErrorMessage); + } + } + + private class InvalidJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + throw new InvalidOperationException(JsonErrorMessage); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new InvalidOperationException(JsonErrorMessage); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new InvalidOperationException(JsonErrorMessage); + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/JsonTransformationConverterTest.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/JsonTransformationConverterTest.cs new file mode 100644 index 0000000000000..b59e6e1f7e951 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/JsonTransformationConverterTest.cs @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.Rest.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; +using Microsoft.Rest.Azure; + +namespace Microsoft.Rest.ClientRuntime.Azure.Test +{ + [Collection("Serialization Tests")] + public class JsonTransformationConverterTest + { + [Fact] + public void TestResourceSerialization() + { + var sampleJson = @"{ + ""Location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + }, + ""properties"": { + ""size"": ""3"", + ""child1"": { + ""@child.key"": ""key"" + }, + ""child"": { + ""dType"": ""SampleResourceChild1"", + ""properties"": { + ""name1"": ""name1"" + } + } + }, + ""plan"": ""testPlan"" +}"; + var sampleResource = new SampleResource() + { + Size = "3", + ChildKey = "key", + Child = new SampleResourceChild1() + { + ChildName1 = "name1" + }, + Location = "EastUS", + Plan = "testPlan", + }; + sampleResource.Tags = new Dictionary(); + sampleResource.Tags["tag1"] = "value1"; + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new TransformationJsonConverter()); + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + var serializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + serializeSettings.Converters.Add(new TransformationJsonConverter()); + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + + var deserializedResource = JsonConvert.DeserializeObject(sampleJson, deserializeSettings); + var jsonoverProcessed = JsonConvert.SerializeObject(deserializedResource, serializeSettings); + + Assert.Equal(sampleJson, jsonoverProcessed); + + string json = JsonConvert.SerializeObject(sampleResource, serializeSettings); + Assert.Equal(sampleJson, json); + } + + [Fact] + public void TestResourceSerializationWithPolymorphism() + { + var sampleResource = new SampleResource() + { + Size = "3", + Child = new SampleResourceChild1(), + Location = "EastUS" + }; + sampleResource.Tags = new Dictionary(); + sampleResource.Tags["tag1"] = "value1"; + var serializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + serializeSettings.Converters.Add(new TransformationJsonConverter()); + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + string json = JsonConvert.SerializeObject(sampleResource, serializeSettings); + Assert.Equal(@"{ + ""dType"": ""SampleResource"", + ""Location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + }, + ""properties"": { + ""size"": ""3"", + ""child"": { + ""dType"": ""SampleResourceChild1"" + } + } +}", json); + } + + [Fact] + public void TestResourceWithNullPropertiesSerialization() + { + var sampleResource = new SampleResource() + { + Location = "EastUS" + }; + sampleResource.Tags = new Dictionary(); + sampleResource.Tags["tag1"] = "value1"; + var serializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + serializeSettings.Converters.Add(new TransformationJsonConverter()); + serializeSettings.Converters.Add(new PolymorphicSerializeJsonConverter("dType")); + string json = JsonConvert.SerializeObject(sampleResource, serializeSettings); + Assert.Equal(@"{ + ""Location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + } +}", json); + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new TransformationJsonConverter()); + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + var deserializedResource = JsonConvert.DeserializeObject(json, deserializeSettings); + var jsonoverProcessed = JsonConvert.SerializeObject(deserializedResource, serializeSettings); + + Assert.Equal(json, jsonoverProcessed); + } + + [Fact] + public void TestProvisioningStateDeserialization() + { + var expected = @"{ + ""Location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + }, + ""properties"": { + ""size"": ""3"", + ""provisioningState"": ""some string"", + ""child"": { + ""dType"": ""SampleResourceChild1"", + ""name1"": ""name1"", + ""id"": ""child"" + } + } + }"; + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new TransformationJsonConverter()); + deserializeSettings.Converters.Add(new PolymorphicDeserializeJsonConverter("dType")); + var deserializedResource = JsonConvert.DeserializeObject(expected, deserializeSettings); + + Assert.Equal("some string", deserializedResource.ProvisioningState); + } + + [Fact] + public void TestDeserializationOfResourceWithConflictingProperties() + { + var expected = @"{ + ""id"": ""123"", + ""location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + }, + ""properties"": { + ""size"": ""3"", + ""provisioningState"": ""some string"", + ""location"": ""Special Location"", + ""id"": ""Special Id"" + } + }"; + + var deserializeSettings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ReferenceLoopHandling = ReferenceLoopHandling.Serialize, + ContractResolver = new ReadOnlyJsonContractResolver() + }; + deserializeSettings.Converters.Add(new TransformationJsonConverter()); + var deserializedResource = JsonConvert.DeserializeObject(expected, deserializeSettings); + + Assert.Equal("Special Location", deserializedResource.SampleResourceWithConflictLocation); + Assert.Equal("Special Id", deserializedResource.SampleResourceWithConflictId); + Assert.Equal("123", deserializedResource.Id); + + var expectedSerializedJson = @"{ + ""location"": ""EastUS"", + ""tags"": { + ""tag1"": ""value1"" + }, + ""properties"": { + ""location"": ""Special Location"", + ""id"": ""Special Id"" + } +}"; + var newJson = JsonConvert.SerializeObject(deserializedResource, deserializeSettings); + Assert.Equal(expectedSerializedJson, newJson); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Microsoft.Rest.ClientRuntime.Tests.xproj b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Microsoft.Rest.ClientRuntime.Tests.xproj new file mode 100644 index 0000000000000..7ee6671508027 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Microsoft.Rest.ClientRuntime.Tests.xproj @@ -0,0 +1,24 @@ + + + + ..\..\..\ + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + f7f20e35-43ee-4fcc-bf83-7bf93b192bc8 + Microsoft.Rest.ClientRuntime.Tests + .\obj + .\bin\ + + + 2.0 + + + + + + + + diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Properties/AssemblyInfo.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000..49b9c869e93e8 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using System.Resources; + +[assembly: AssemblyTitle("Microsoft Rest Client Runtime Tests")] +[assembly: AssemblyDescription("Tests for the Microsoft Rest Client Runtime.")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.4.2.0")] +[assembly: AssemblyInformationalVersion("1.4.2.0")] + +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Microsoft AutoRest ClientRuntime Tests")] +[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Resources/Animal.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Resources/Animal.cs new file mode 100644 index 0000000000000..d2f8f0c00586d --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Resources/Animal.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Microsoft.Rest.ClientRuntime.Tests.Resources +{ + [JsonObject("animal")] + public class Animal + { + [JsonProperty("name")] + public string Name { get; set; } + } + + [JsonObject("dog")] + public class Dog : Animal + { + [JsonProperty("likesDogfood")] + public bool LikesDogfood { get; set; } + } + + [JsonObject("cat")] + public class Cat : Animal + { + [JsonProperty("likesMice")] + public bool LikesMice { get; set; } + + [JsonProperty("dislikes")] + public Animal Dislikes { get; set; } + } + + [JsonObject("siamese")] + public class Martian : Alien + { + [JsonProperty("robot")] + public string Robot { get; set; } + } + + [JsonObject("siamese")] + public class Siamese : Cat + { + [JsonProperty("color")] + public string Color { get; private set; } + } + + [JsonObject("alien")] + public class Alien + { + public Alien() + { + + } + + public Alien(string color) + { + Color = color; + } + + private string _planet; + + [JsonProperty("planet")] + public string Planet { set { _planet = value; } } + + [JsonProperty("color")] + public string Color { get; private set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("body")] + public dynamic Body { get; set; } + + public string GetPlanetName() + { + return _planet; + } + } + + public class Zoo + { + public int Id { get; set; } + private List _animals = new List(); + public List Animals { get { return _animals; } set { _animals = value; } } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Resources/DateTestObject.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Resources/DateTestObject.cs new file mode 100644 index 0000000000000..726260c9fda5c --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Resources/DateTestObject.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.Rest.Serialization; +using Newtonsoft.Json; + +namespace Microsoft.Rest.ClientRuntime.Tests.Resources +{ + [JsonObject("dateTest")] + public class DateTestObject + { + [JsonProperty("d")] + [JsonConverter(typeof(DateJsonConverter))] + public DateTime Date { get; set; } + + [JsonProperty("dt")] + public DateTime DateTime { get; set; } + + [JsonProperty("dn")] + public DateTime? DateNullable { get; set; } + + [JsonProperty("dtn")] + [JsonConverter(typeof(DateJsonConverter))] + public DateTime? DateTimeNullable { get; set; } + + [JsonProperty("dtoc")] + [JsonConverter(typeof(DateJsonConverter))] + public DateTimeOffset DateTimeOffsetWithConverter { get; set; } + + [JsonProperty("dtonc")] + [JsonConverter(typeof(DateJsonConverter))] + public DateTimeOffset? DateTimeOffsetNullableWithConverter { get; set; } + + [JsonProperty("dto")] + public DateTimeOffset DateTimeOffset { get; set; } + + [JsonProperty("dton")] + public DateTimeOffset? DateTimeOffsetNullable { get; set; } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Resources/SampleResource.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Resources/SampleResource.cs new file mode 100644 index 0000000000000..bfc4d90791560 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Resources/SampleResource.cs @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.Rest.Serialization; +using Newtonsoft.Json; + +namespace Microsoft.Rest.Azure +{ + /// + /// Information for resource. + /// + [JsonTransformation] + public partial class SampleResource + { + /// + /// Gets the ID of the resource. + /// + [JsonProperty("id")] + public string Id { get; private set; } + + /// + /// Gets the name of the resource. + /// + [JsonProperty("name")] + public string Name { get; private set; } + + /// + /// Gets the type of the resource. + /// + [JsonProperty] + public string Type { get; private set; } + + /// + /// Required. Gets or sets the location of the resource. + /// + [JsonProperty] + public string Location { get; set; } + + /// + /// Optional. Gets or sets the tags attached to the resource. + /// + [JsonProperty("tags")] + public IDictionary Tags { get; set; } + + /// + /// Optional. Gets or sets the size of the resource. + /// + [JsonProperty("properties.size")] + public string Size { get; set; } + + /// + /// Gets the key of the child resource. + /// + [JsonProperty("properties.child1.@child\\.key")] + public string ChildKey { get; set; } + + /// + /// Optional. Gets or sets the child resource. + /// + [JsonProperty("properties.child")] + public SampleResourceChild Child { get; set; } + + /// + /// Optional. Gets or sets the details. + /// + [JsonProperty("properties.name")] + public dynamic Details { get; set; } + + /// + /// Optional. Gets or sets the plan. + /// + [JsonProperty("plan")] + public string Plan { get; set; } + + /// + /// Optional. Gets or sets the provisioning state. + /// + [JsonProperty("properties.provisioningState")] + public string ProvisioningState { get; set; } + + /// + /// Validate the object. Throws ArgumentException or ArgumentNullException if validation fails. + /// + public virtual void Validate() + { + if (Location == null) + { + throw new ValidationException(ValidationRules.CannotBeNull, "Location"); + } + } + + } + + /// + /// Information for resource. + /// + public abstract class SampleResourceChild + { + /// + /// Gets the ID of the resource. + /// + [JsonProperty("id")] + public string Id { get; private set; } + } + + /// + /// Information for resource. + /// + [JsonTransformation] + public partial class SampleResourceChild1 : SampleResourceChild + { + /// + /// Optional. Gets or sets the Id of the resource. + /// + [JsonProperty("properties.name1")] + public string ChildName1 { get; set; } + } + + /// + /// Information for resource. + /// + [JsonTransformation] + public partial class SampleResourceChild2 : SampleResourceChild + { + /// + /// Optional. Gets or sets the Id of the resource. + /// + [JsonProperty("properties.name2")] + public string ChildName2 { get; set; } + } + + /// + /// Information for resource. + /// + [JsonTransformation] + public partial class SampleResourceWithConflict + { + /// + /// Gets the ID of the resource. + /// + [JsonProperty("id")] + public string Id { get; private set; } + + /// + /// Gets the name of the resource. + /// + [JsonProperty("name")] + public string Name { get; private set; } + + /// + /// Gets the type of the resource. + /// + [JsonProperty("type")] + public string Type { get; private set; } + + /// + /// Required. Gets or sets the location of the resource. + /// + [JsonProperty("location")] + public string Location { get; set; } + + /// + /// Optional. Gets or sets the tags attached to the resource. + /// + [JsonProperty("tags")] + public IDictionary Tags { get; set; } + + /// + /// Optional. Gets or sets the special location of the resource. + /// + [JsonProperty("properties.location")] + public string SampleResourceWithConflictLocation { get; set; } + + /// + /// Optional. Gets or sets the special id resource. + /// + [JsonProperty("properties.id")] + public string SampleResourceWithConflictId { get; set; } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/ServiceClientTests.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/ServiceClientTests.cs new file mode 100644 index 0000000000000..0c693ba21d56c --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/ServiceClientTests.cs @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using Microsoft.Rest.ClientRuntime.Tests.Fakes; +using Microsoft.Rest.TransientFaultHandling; +using Xunit; +using System.Net.Http.Headers; +using System.Diagnostics; + +namespace Microsoft.Rest.ClientRuntime.Tests +{ + public class ServiceClientTests + { + [Fact] + public void ClientAddHandlerToPipelineAddsHandler() + { + var fakeClient = new FakeServiceClient(new HttpClientHandler(), new BadResponseDelegatingHandler()); + var result2 = fakeClient.DoStuffSync(); + Assert.Equal(HttpStatusCode.InternalServerError, result2.StatusCode); + } + + [Fact] + public void ClientAddHandlersToPipelineAddSingleHandler() + { + var fakeClient = new FakeServiceClient(new HttpClientHandler(), + new BadResponseDelegatingHandler() + ); + + var result2 = fakeClient.DoStuffSync(); + Assert.Equal(HttpStatusCode.InternalServerError, result2.StatusCode); + } + + [Fact] + public void ClientAddHandlersToPipelineAddMultipleHandler() + { + var fakeClient = new FakeServiceClient(new HttpClientHandler(), + new AddHeaderResponseDelegatingHandler("foo", "bar"), + new BadResponseDelegatingHandler() + ); + + var result2 = fakeClient.DoStuffSync(); + Assert.Equal(result2.Headers.GetValues("foo").FirstOrDefault(), "bar"); + Assert.Equal(HttpStatusCode.InternalServerError, result2.StatusCode); + } + + [Fact] + public void ClientAddHandlersToPipelineChainsEmptyHandler() + { + var handlerA = new AppenderDelegatingHandler("A"); + var handlerB = new AppenderDelegatingHandler("B"); + var handlerC = new AppenderDelegatingHandler("C"); + + var fakeClient = new FakeServiceClient(new HttpClientHandler(), + handlerA, handlerB, handlerC, + new MirrorDelegatingHandler()); + + var response = fakeClient.DoStuffSync("Text").Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.Equal("Text+A+B+C", response); + } + + [Fact] + public void ClientAddHandlersToPipelineChainsNestedHandler() + { + var handlerA = new AppenderDelegatingHandler("A"); + var handlerB = new AppenderDelegatingHandler("B"); + var handlerC = new AppenderDelegatingHandler("C"); + handlerA.InnerHandler = handlerB; + handlerB.InnerHandler = handlerC; + var handlerD = new AppenderDelegatingHandler("D"); + var handlerE = new AppenderDelegatingHandler("E"); + handlerD.InnerHandler = handlerE; + handlerE.InnerHandler = new MirrorMessageHandler("F"); + + var fakeClient = new FakeServiceClient(new HttpClientHandler(), + handlerA, handlerD, + new MirrorDelegatingHandler()); + + var response = fakeClient.DoStuffSync("Text").Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.Equal("Text+A+B+C+D+E", response); + } + + [Fact] + public void ClientWithoutHandlerWorks() + { + var fakeClient = new FakeServiceClient(new HttpClientHandler(), + new MirrorDelegatingHandler()); + + var response = fakeClient.DoStuffSync("Text").Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.Equal("Text", response); + } + + [Fact] + public void RetryHandlerRetriesWith500Errors() + { + var fakeClient = new FakeServiceClient(new FakeHttpHandler()); + int attemptsFailed = 0; + + var retryPolicy = new RetryPolicy(2); + fakeClient.SetRetryPolicy(retryPolicy); + var retryHandler = fakeClient.HttpMessageHandlers.OfType().FirstOrDefault(); + retryPolicy.Retrying += (sender, args) => { attemptsFailed++; }; + + var result = fakeClient.DoStuffSync(); + Assert.Equal(HttpStatusCode.InternalServerError, result.StatusCode); + Assert.Equal(2, attemptsFailed); + } + + [Fact] + public void RetryHandlerRetriesWith500ErrorsAndSucceeds() + { + var fakeClient = new FakeServiceClient(new FakeHttpHandler() {NumberOfTimesToFail = 1}); + int attemptsFailed = 0; + + var retryPolicy = new RetryPolicy(2); + fakeClient.SetRetryPolicy(retryPolicy); + var retryHandler = fakeClient.HttpMessageHandlers.OfType().FirstOrDefault(); + retryPolicy.Retrying += (sender, args) => { attemptsFailed++; }; + + var result = fakeClient.DoStuffSync(); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(1, attemptsFailed); + } + + [Fact] + public void RetryHandlerDoesntRetryFor400Errors() + { + var fakeClient = new FakeServiceClient(new FakeHttpHandler() {StatusCodeToReturn = HttpStatusCode.Conflict}); + int attemptsFailed = 0; + + var retryPolicy = new RetryPolicy(2); + fakeClient.SetRetryPolicy(retryPolicy); + var retryHandler = fakeClient.HttpMessageHandlers.OfType().FirstOrDefault(); + retryPolicy.Retrying += (sender, args) => { attemptsFailed++; }; + + var result = fakeClient.DoStuffSync(); + Assert.Equal(HttpStatusCode.Conflict, result.StatusCode); + Assert.Equal(0, attemptsFailed); + } + + [Fact] + public void HeadersAndPayloadAreNotDisposed() + { + FakeServiceClient fakeClient = null; + try + { + fakeClient = new FakeServiceClient(new HttpClientHandler(), + new MirrorDelegatingHandler()); + var response = fakeClient.DoStuffAndThrowSync("Text"); + Assert.True(false); + } + catch (HttpOperationException ex) + { + fakeClient.Dispose(); + fakeClient = null; + GC.Collect(); + Assert.NotNull(ex.Request); + Assert.NotNull(ex.Response); + Assert.Equal("2013-11-01", ex.Request.Headers["x-ms-version"].First()); + Assert.Equal("Text", ex.Response.Content); + } + } + + [Fact] + public void AddUserAgentInfoWithoutVersion() + { + string defaultProductName = "FxVersion"; + string testProductName = "TestProduct"; + Version defaultProductVer, testProductVer; + + FakeServiceClient fakeClient = new FakeServiceClient(new FakeHttpHandler()); + fakeClient.SetUserAgent(testProductName); + HttpResponseMessage response = fakeClient.DoStuffSync(); + HttpHeaderValueCollection userAgentValueCollection = fakeClient.HttpClient.DefaultRequestHeaders.UserAgent; + + var defaultProduct = userAgentValueCollection.Where((p) => p.Product.Name.Equals(defaultProductName)).FirstOrDefault(); + var testProduct = userAgentValueCollection.Where((p) => p.Product.Name.Equals(testProductName)).FirstOrDefault(); + + Version.TryParse(defaultProduct.Product.Version, out defaultProductVer); + Version.TryParse(testProduct.Product.Version, out testProductVer); + + Assert.True(defaultProduct.Product.Name.Equals(defaultProductName)); + Assert.NotNull(defaultProductVer); + + Assert.True(testProduct.Product.Name.Equals(testProductName)); + Assert.NotNull(testProductVer); + } + + [Fact] + public void AddUserAgentInfoWithVersion() + { + string defaultProductName = "FxVersion"; + string testProductName = "TestProduct"; + string testProductVersion = "1.0.0.0"; + Version defaultProductVer, testProductVer; + + FakeServiceClient fakeClient = new FakeServiceClient(new FakeHttpHandler()); + fakeClient.SetUserAgent(testProductName, testProductVersion); + HttpResponseMessage response = fakeClient.DoStuffSync(); + HttpHeaderValueCollection userAgentValueCollection = fakeClient.HttpClient.DefaultRequestHeaders.UserAgent; + + var defaultProduct = userAgentValueCollection.Where((p) => p.Product.Name.Equals(defaultProductName)).FirstOrDefault(); + var testProduct = userAgentValueCollection.Where((p) => p.Product.Name.Equals(testProductName)).FirstOrDefault(); + + Version.TryParse(defaultProduct.Product.Version, out defaultProductVer); + Version.TryParse(testProduct.Product.Version, out testProductVer); + + Assert.True(defaultProduct.Product.Name.Equals(defaultProductName)); + Assert.NotNull(defaultProductVer); + + Assert.True(testProduct.Product.Name.Equals(testProductName)); + Assert.True(testProduct.Product.Version.Equals(testProductVersion)); + } + + #if net45 + [Fact] + public void VerifyOsInfoInUserAgent() + { + + string osInfoProductName = "OSName"; + + FakeServiceClient fakeClient = new FakeServiceClient(new FakeHttpHandler()); + HttpResponseMessage response = fakeClient.DoStuffSync(); + HttpHeaderValueCollection userAgentValueCollection = fakeClient.HttpClient.DefaultRequestHeaders.UserAgent; + + var osProduct = userAgentValueCollection.Where((p) => p.Product.Name.Equals(osInfoProductName)).FirstOrDefault(); + + Assert.NotEmpty(osProduct.Product.Name); + Assert.NotEmpty(osProduct.Product.Version); + } + #endif + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Tracing/CloudTracingExtensionsTest.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Tracing/CloudTracingExtensionsTest.cs new file mode 100644 index 0000000000000..df5eda500f382 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/Tracing/CloudTracingExtensionsTest.cs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using Xunit; + +namespace Microsoft.Rest.ClientRuntime.Tests.Tracing +{ + public class CloudTracingExtensionsTest + { + [Fact] + public void HttpRequestMessageAsFormattedStringThrowsArgumentNullException() + { + Assert.Throws(() => HttpExtensions.AsFormattedString((HttpRequestMessage) null)); + } + + [Fact] + public void HttpRequestMessageAsFormattedStringHandlesEmptyRequests() + { + using (var httpRequest = new HttpRequestMessage()) + { + var formattedString = httpRequest.AsFormattedString(); + Assert.Contains("Method: GET", formattedString); + } + } + + [Fact] + public void HttpRequestMessageAsFormattedStringHandlesRequestsWithHeaders() + { + using (var httpRequest = new HttpRequestMessage(HttpMethod.Get, "http://www.windowsazure.com/test")) + { + httpRequest.Headers.Add("x-ms-version", "2013-11-01"); + var formattedString = httpRequest.AsFormattedString(); + Assert.Contains("Method: GET", formattedString); + Assert.Contains("RequestUri: 'http://www.windowsazure.com/test'", formattedString); + Assert.Contains("x-ms-version: 2013-11-01", formattedString); + } + } + + [Fact] + public void HttpRequestMessageAsFormattedStringHandlesRequestsWithContent() + { + using (var httpRequest = new HttpRequestMessage(HttpMethod.Get, "http://www.windowsazure.com/test")) + { + httpRequest.Content = new StringContent(""); + var formattedString = httpRequest.AsFormattedString(); + Assert.Contains("Method: GET", formattedString); + Assert.Contains("RequestUri: 'http://www.windowsazure.com/test'", formattedString); + Assert.Contains("", formattedString); + } + } + + [Fact] + public void HttpResponseMessageAsFormattedStringThrowsArgumentNullException() + { + Assert.Throws(() => HttpExtensions.AsFormattedString((HttpResponseMessage) null)); + } + + [Fact] + public void HttpResponseMessageAsFormattedStringHandlesEmptyRequests() + { + using (var httpRequest = new HttpResponseMessage()) + { + var formattedString = httpRequest.AsFormattedString(); + Assert.Contains("StatusCode: 200", formattedString); + } + } + + [Fact] + public void HttpResponseMessageAsFormattedStringHandlesRequestsWithHeaders() + { + using (var httpRequest = new HttpResponseMessage(HttpStatusCode.OK)) + { + httpRequest.Headers.Add("x-ms-version", "2013-11-01"); + var formattedString = httpRequest.AsFormattedString(); + Assert.Contains("StatusCode: 200", formattedString); + Assert.Contains("x-ms-version: 2013-11-01", formattedString); + } + } + + [Fact] + public void HttpResponseMessageAsFormattedStringHandlesRequestsWithContent() + { + using (var httpRequest = new HttpResponseMessage(HttpStatusCode.OK)) + { + httpRequest.Content = new StringContent(""); + var formattedString = httpRequest.AsFormattedString(); + Assert.Contains("StatusCode: 200", formattedString); + Assert.Contains("", formattedString); + } + } + + [Fact] + public void DictionaryAsFormattedStringReturnsEmptyBracesForNull() + { + var formattedString = HttpExtensions.AsFormattedString((Dictionary) null); + Assert.Equal("{}", formattedString); + } + + [Fact] + public void DictionaryAsFormattedStringReturnsEmptyBracesForEmptySet() + { + var formattedString = new Dictionary().AsFormattedString(); + Assert.Equal("{}", formattedString); + } + + [Fact] + public void DictionaryAsFormattedStringReturnsSet() + { + var parameters = new Dictionary(); + parameters["a"] = 1; + parameters["b"] = "str"; + parameters["c"] = true; + var formattedString = parameters.AsFormattedString(); + Assert.Equal("{a=1,b=str,c=True}", formattedString); + } + + [Fact] + public void DictionaryAsFormattedStringWorksWithNulls() + { + var parameters = new Dictionary(); + parameters["a"] = 1; + parameters["b"] = "str"; + parameters["c"] = null; + var formattedString = parameters.AsFormattedString(); + Assert.Equal("{a=1,b=str,c=}", formattedString); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/TransientFaultHandling/DefaultHttpErrorDetectionStrategyTests.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/TransientFaultHandling/DefaultHttpErrorDetectionStrategyTests.cs new file mode 100644 index 0000000000000..544d63c37e967 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/TransientFaultHandling/DefaultHttpErrorDetectionStrategyTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net; +using Microsoft.Rest.TransientFaultHandling; +using Xunit; + +namespace Microsoft.Rest.ClientRuntime.Tests.TransientFaultHandling +{ + /// + /// Implements general test cases for http error detections. + /// + public class DefaultHttpErrorDetectionStrategyTests + { + [Theory] + [InlineData(HttpStatusCode.InternalServerError)] + [InlineData(HttpStatusCode.RequestTimeout)] + [InlineData(HttpStatusCode.BadGateway)] + public void ResponseCodeIsConsideredTransient(HttpStatusCode code) + { + var strategy = new HttpStatusCodeErrorDetectionStrategy(); + Assert.True(strategy.IsTransient(new HttpRequestWithStatusException {StatusCode = code})); + } + + [Theory] + [InlineData(HttpStatusCode.NotImplemented)] + [InlineData(HttpStatusCode.HttpVersionNotSupported)] + public void ResponseCodeIsNotConsideredTransient(HttpStatusCode code) + { + var strategy = new HttpStatusCodeErrorDetectionStrategy(); + Assert.False(strategy.IsTransient(new HttpRequestWithStatusException {StatusCode = code})); + } + + [Fact] + public void BadExceptionIsNotConsideredTransient() + { + var strategy = new HttpStatusCodeErrorDetectionStrategy(); + Assert.False(strategy.IsTransient(new InvalidOperationException())); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/TransientFaultHandling/GeneralRetryPolicyTests.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/TransientFaultHandling/GeneralRetryPolicyTests.cs new file mode 100644 index 0000000000000..c2eca5eae27cb --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/TransientFaultHandling/GeneralRetryPolicyTests.cs @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.Rest.TransientFaultHandling; +using Xunit; + +namespace Microsoft.Rest.ClientRuntime.Tests.TransientFaultHandling +{ + /// + /// Implements general test cases for retry policies. + /// + public class GeneralRetryPolicyTests + { + [Fact] + public void TestNegativeRetryCount() + { + try + { + // First, instantiate a policy directly bypassing the configuration data validation. + new RetryPolicy(-1); + Assert.True(false, "When the RetryCount is negative, the retry policy should throw an exception."); + } + catch (ArgumentOutOfRangeException ex) + { + Assert.Equal("retryCount", ex.ParamName); + } + } + + [Fact] + public void TestNegativeRetryInterval() + { + try + { + // First, instantiate a policy directly bypassing the configuration data validation. + new RetryPolicy(3, TimeSpan.FromMilliseconds(-2)); + Assert.True(false, "When the RetryInterval is negative, the retry policy should throw an exception."); + } + catch (ArgumentOutOfRangeException ex) + { + Assert.Equal("retryInterval", ex.ParamName); + } + } + + [Fact] + public void TestNegativeMinBackoff() + { + try + { + // First, instantiate a policy directly bypassing the configuration data validation. + new RetryPolicy(3, TimeSpan.FromMilliseconds(-1), + TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(100)); + Assert.True(false, "When the MinBackoff is negative, the retry policy should throw an exception."); + } + catch (ArgumentOutOfRangeException ex) + { + Assert.Equal("minBackoff", ex.ParamName); + } + } + + [Fact] + public void TestNegativeMaxBackoff() + { + try + { + // First, instantiate a policy directly bypassing the configuration data validation. + new RetryPolicy(3, TimeSpan.FromMilliseconds(100), + TimeSpan.FromMilliseconds(-2), TimeSpan.FromMilliseconds(100)); + Assert.True(false, "When the MaxBackoff is negative, the retry policy should throw an exception."); + } + catch (ArgumentOutOfRangeException ex) + { + Assert.Equal("maxBackoff", ex.ParamName); + } + } + + [Fact] + public void TestNegativeDeltaBackoff() + { + try + { + // First, instantiate a policy directly bypassing the configuration data validation. + new RetryPolicy(3, TimeSpan.FromMilliseconds(100), + TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(-1)); + Assert.True(false, "When the DeltaBackoff is negative, the retry policy should throw an exception."); + } + catch (ArgumentOutOfRangeException ex) + { + Assert.Equal("deltaBackoff", ex.ParamName); + } + } + + [Fact] + public void TestMinBackoffGreaterThanMax() + { + try + { + // First, instantiate a policy directly bypassing the configuration data validation. + new RetryPolicy(3, TimeSpan.FromMilliseconds(1000), + TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100)); + Assert.True(false, + "When the MinBackoff greater than MaxBackoff, the retry policy should throw an exception."); + } + catch (ArgumentOutOfRangeException ex) + { + Assert.Equal("minBackoff", ex.ParamName); + } + } + + internal static void TestRetryPolicy(RetryPolicy retryPolicy, out int retryCount, out TimeSpan totalDelay) + { + var callbackCount = 0; + double totalDelayInMs = 0; + + retryPolicy.Retrying += (sender, args) => + { + callbackCount++; + totalDelayInMs += args.Delay.TotalMilliseconds; + }; + + try + { + retryPolicy.ExecuteAction(() => { throw new TimeoutException("Forced Exception"); }); + } + catch (TimeoutException ex) + { + Assert.Equal("Forced Exception", ex.Message); + } + + retryCount = callbackCount; + totalDelay = TimeSpan.FromMilliseconds(totalDelayInMs); + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/TransientFaultHandling/RetryConditionTest.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/TransientFaultHandling/RetryConditionTest.cs new file mode 100644 index 0000000000000..b00b0dfe94b49 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/TransientFaultHandling/RetryConditionTest.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.Rest.TransientFaultHandling; +using Xunit; + +namespace Microsoft.Rest.ClientRuntime.Tests.TransientFaultHandling +{ + public class RetryConditionTest + { + [Fact] + public void PropertiesAreSetByConstrutor() + { + var condition = new RetryCondition(true, TimeSpan.FromSeconds(1)); + Assert.Equal(true, condition.RetryAllowed); + Assert.Equal(TimeSpan.FromSeconds(1), condition.DelayBeforeRetry); + + condition = new RetryCondition(false, TimeSpan.Zero); + Assert.Equal(false, condition.RetryAllowed); + Assert.Equal(TimeSpan.Zero, condition.DelayBeforeRetry); + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/ValidationExceptionTests.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/ValidationExceptionTests.cs new file mode 100644 index 0000000000000..330db5b3598af --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/ValidationExceptionTests.cs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Linq; +using System.Net; +using System.Net.Http; +using Microsoft.Rest.ClientRuntime.Tests.Fakes; +using Microsoft.Rest.TransientFaultHandling; +using Xunit; + +namespace Microsoft.Rest.ClientRuntime.Tests +{ + public class ValidationExceptionTests + { + [Fact] + public void VerifyNotNull() + { + var validationException = new ValidationException(ValidationRules.CannotBeNull, "Id"); + + Assert.Equal(ValidationRules.CannotBeNull, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' cannot be null.", validationException.ToString()); + } + + [Fact] + public void VerifyMaximum() + { + var validationException = new ValidationException(ValidationRules.InclusiveMaximum, "Id", 20); + + Assert.Equal(ValidationRules.InclusiveMaximum, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' exceeds maximum value of '20'.", validationException.ToString()); + } + + [Fact] + public void VerifyExclusiveMaximum() + { + var validationException = new ValidationException(ValidationRules.ExclusiveMaximum, "Id", 30); + + Assert.Equal(ValidationRules.ExclusiveMaximum, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' is equal or exceeds maximum value of '30'.", validationException.ToString()); + } + + [Fact] + public void VerifyMinimum() + { + var validationException = new ValidationException(ValidationRules.InclusiveMinimum, "Id", 3); + + Assert.Equal(ValidationRules.InclusiveMinimum, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' is less than minimum value of '3'.", validationException.ToString()); + } + + [Fact] + public void VerifyExclusiveMinimum() + { + var validationException = new ValidationException(ValidationRules.ExclusiveMinimum, "Id", -1); + + Assert.Equal(ValidationRules.ExclusiveMinimum, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' is less than or equal minimum value of '-1'.", validationException.ToString()); + } + + [Fact] + public void VerifyMaxLenth() + { + var validationException = new ValidationException(ValidationRules.MaxLength, "Id", 100); + + Assert.Equal(ValidationRules.MaxLength, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' exceeds maximum length of '100'.", validationException.ToString()); + } + + [Fact] + public void VerifyMinLenth() + { + var validationException = new ValidationException(ValidationRules.MinLength, "Id", 12); + + Assert.Equal(ValidationRules.MinLength, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' is less than minimum length of '12'.", validationException.ToString()); + } + + [Fact] + public void VerifyPattern() + { + var validationException = new ValidationException(ValidationRules.Pattern, "Id", "\\w+"); + + Assert.Equal(ValidationRules.Pattern, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal("\\w+", validationException.Details); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' does not match expected pattern '\\w+'.", validationException.ToString()); + } + + [Fact] + public void VerifyMaxItems() + { + var validationException = new ValidationException(ValidationRules.MaxItems, "Id", 2); + + Assert.Equal(ValidationRules.MaxItems, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' exceeds maximum item count of '2'.", validationException.ToString()); + } + + [Fact] + public void VerifyMinItems() + { + var validationException = new ValidationException(ValidationRules.MinItems, "Id", 1); + + Assert.Equal(ValidationRules.MinItems, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' contains less items than '1'.", validationException.ToString()); + } + + [Fact] + public void VerifyUniqueItems() + { + var validationException = new ValidationException(ValidationRules.UniqueItems, "Id"); + + Assert.Equal(ValidationRules.UniqueItems, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' collection contains duplicate items.", validationException.ToString()); + } + + [Fact] + public void VerifyEnum() + { + var validationException = new ValidationException(ValidationRules.Enum, "Id", "red, green, blue"); + + Assert.Equal(ValidationRules.Enum, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' cannot have value other than 'red, green, blue'.", validationException.ToString()); + } + + [Fact] + public void VerifyMultipleOf() + { + var validationException = new ValidationException(ValidationRules.MultipleOf, "Id", 5); + + Assert.Equal(ValidationRules.MultipleOf, validationException.Rule); + Assert.Equal("Id", validationException.Target); + Assert.Equal(5, validationException.Details); + Assert.Equal("Microsoft.Rest.ValidationException: 'Id' has to be multiple of '5'.", validationException.ToString()); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/project.json b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/project.json new file mode 100644 index 0000000000000..0eb0e969c298b --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tests/project.json @@ -0,0 +1,30 @@ +{ + "version": "2.0.0-preview", + "authors": [ "Microsoft" ], + + "packOptions": { + "summary": "ClientRuntime Tests.", + "tags": [ "Microsoft AutoRest ClientRuntime REST" ], + "projectUrl": "https://github.com/Azure/AutoRest", + "licenseUrl": "https://raw.githubusercontent.com/Microsoft/dotnet/master/LICENSE" + }, + + "testRunner": "xunit", + "frameworks": { + "netcoreapp1.0": { + "buildOptions": { "define": [ "PORTABLE" ] }, + "imports": ["dnxcore50", "portable-net45+win8"] + } + }, + "dependencies": { + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.0.0" + }, + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.Rest.ClientRuntime.Azure": "[3.3.2,4.0.0)", + "Microsoft.Rest.ClientRuntime.Azure.Authentication": "[2.2.8-preview,3.0.0)", + "xunit": "2.2.0-beta2-build3300", + "dotnet-test-xunit": "2.2.0-preview2-build1029" + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/EtwTracingInterceptorTest.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/EtwTracingInterceptorTest.cs new file mode 100644 index 0000000000000..022b9d63e9bfb --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/EtwTracingInterceptorTest.cs @@ -0,0 +1,267 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.Diagnostics.Tracing; +using Microsoft.Diagnostics.Tracing.Session; +using Microsoft.Rest.Tracing.Etw; +using Xunit; + +namespace Microsoft.Rest.ClientRuntime.Tracing.Tests +{ + internal class EtwTracingInterceptorTest : IDisposable + { + // Globally defined session is needed since we are running multiple traces + private readonly TraceEventSession _eventSession; + + public EtwTracingInterceptorTest() + { + _eventSession = new TraceEventSession(Guid.NewGuid().ToString()); + var eventSourceGuid = TraceEventProviders.GetEventSourceGuidFromName("Microsoft-WindowsAzure"); + _eventSession.EnableProvider(eventSourceGuid); + } + + public void Dispose() + { + _eventSession.Stop(); + _eventSession.Dispose(); + } + + [Theory] + [InlineData("test")] + [InlineData("")] + [InlineData(null)] + public void InformationLogsEventWithMessage(string message) + { + EtwTracingHelper("Information", new[] {"Message"}, + () => + { + EtwTracingInterceptor etwTracer = new EtwTracingInterceptor(); + etwTracer.Information(message); + }, + dict => + { + message = message ?? string.Empty; + + Assert.Equal(message, dict["Message"]); + }); + } + + [Theory] + [InlineData("test", "name", "value")] + [InlineData("test", "name", "")] + [InlineData(null, null, null)] + public void ConfigurationLogsEventWithAllParameters(string source, string name, string value) + { + EtwTracingHelper("Configuration", new[] {"Source", "Name", "Value"}, + () => + { + EtwTracingInterceptor etwTracer = new EtwTracingInterceptor(); + etwTracer.Configuration(source, name, value); + }, + dict => + { + source = source ?? string.Empty; + name = name ?? string.Empty; + value = value ?? string.Empty; + + Assert.Equal(source, dict["Source"]); + Assert.Equal(name, dict["Name"]); + Assert.Equal(value, dict["Value"]); + }); + } + + [Fact] + public void EnterLogsEventWithNonNullParams() + { + Dictionary parameters = new Dictionary(); + parameters["a"] = 1; + parameters["b"] = "str"; + parameters["c"] = true; + + + EtwTracingHelper("Enter", new[] {"InvocationId", "Instance", "Method", "Parameters"}, + () => + { + EtwTracingInterceptor etwTracer = new EtwTracingInterceptor(); + etwTracer.EnterMethod("1", 23, "main", parameters); + }, + dict => + { + Assert.Equal("1", dict["InvocationId"]); + Assert.Equal("23", dict["Instance"]); + Assert.Equal("main", dict["Method"]); + Assert.Equal("{a=1,b=str,c=True}", dict["Parameters"]); + }); + } + + [Fact] + public void EnterLogsEventWithNullParams() + { + Dictionary parameters = new Dictionary(); + parameters["a"] = 1; + parameters["b"] = "str"; + parameters["c"] = null; + + + EtwTracingHelper("Enter", new[] {"InvocationId", "Instance", "Method", "Parameters"}, + () => + { + EtwTracingInterceptor etwTracer = new EtwTracingInterceptor(); + etwTracer.EnterMethod("1", null, "main", parameters); + }, + dict => + { + Assert.Equal("1", dict["InvocationId"]); + Assert.Equal("", dict["Instance"]); + Assert.Equal("main", dict["Method"]); + Assert.Equal("{a=1,b=str,c=}", dict["Parameters"]); + }); + } + + [Fact] + public void SendRequestLogsEventWithNonNullRequest() + { + var httpRequest = new HttpRequestMessage(HttpMethod.Get, "http://www.windowsazure.com/test"); + httpRequest.Headers.Add("x-ms-version", "2013-11-01"); + + EtwTracingHelper("SendRequest", new[] {"InvocationId", "Request"}, + () => + { + EtwTracingInterceptor etwTracer = new EtwTracingInterceptor(); + etwTracer.SendRequest("1", httpRequest); + }, + dict => + { + Assert.Equal("1", dict["InvocationId"]); + Assert.Contains("RequestUri: 'http://www.windowsazure.com/test'", dict["Request"]); + Assert.Contains("x-ms-version: 2013-11-01", dict["Request"]); + }); + } + + [Fact] + public void SendRequestLogsEventWithNullRequest() + { + EtwTracingHelper("SendRequest", new[] {"InvocationId", "Request"}, + () => + { + EtwTracingInterceptor etwTracer = new EtwTracingInterceptor(); + etwTracer.SendRequest(null, null); + }, + dict => + { + Assert.Equal("", dict["InvocationId"]); + Assert.Equal("", dict["Request"]); + }); + } + + [Fact] + public void ReceiveResponseLogsEventWithNonNullRequest() + { + using (var httpRequest = new HttpResponseMessage(System.Net.HttpStatusCode.OK)) + { + httpRequest.Content = new StringContent(""); + + EtwTracingHelper("ReceiveResponse", new[] {"InvocationId", "Response"}, + () => + { + EtwTracingInterceptor etwTracer = new EtwTracingInterceptor(); + if (httpRequest != null) etwTracer.ReceiveResponse("1", httpRequest); + }, + dict => + { + Assert.Equal("1", dict["InvocationId"]); + Assert.Contains("", dict["Response"]); + }); + } + } + + [Fact] + public void ReceiveResponseLogsEventWithNullRequest() + { + EtwTracingHelper("ReceiveResponse", new[] {"InvocationId", "Response"}, + () => + { + EtwTracingInterceptor etwTracer = new EtwTracingInterceptor(); + etwTracer.ReceiveResponse(null, null); + }, + dict => + { + Assert.Equal("", dict["InvocationId"]); + Assert.Equal("", dict["Response"]); + }); + } + + [Fact] + public void ExitLogsEventWithNonNullRequest() + { + EtwTracingHelper("Exit", new[] {"InvocationId", "ReturnValue"}, + () => + { + EtwTracingInterceptor etwTracer = new EtwTracingInterceptor(); + etwTracer.ExitMethod("1", 5); + }, + dict => + { + Assert.Equal("1", dict["InvocationId"]); + Assert.Equal("5", dict["ReturnValue"]); + }); + } + + [Fact] + public void ExitLogsEventWithNullRequest() + { + EtwTracingHelper("Exit", new[] {"InvocationId", "ReturnValue"}, + () => + { + EtwTracingInterceptor etwTracer = new EtwTracingInterceptor(); + etwTracer.ExitMethod(null, null); + }, + dict => + { + Assert.Equal("", dict["InvocationId"]); + Assert.Equal("", dict["ReturnValue"]); + }); + } + + private void EtwTracingHelper(string eventName, string[] attributes, Action doAction, + Action> assertAction) + { + Dictionary valuesFromEvent = new Dictionary(); + Action eventDelegate = delegate(TraceEvent data) + { + if (data.EventName == eventName) + { + foreach (var attributeName in attributes) + { + valuesFromEvent[attributeName] = data.PayloadByName(attributeName).ToString(); + } + _eventSession.Source.StopProcessing(); + } + }; + + _eventSession.Source.Dynamic.All += eventDelegate; + +#if NET45 + var task = Task.Run(() => _eventSession.Source.Process()); +#else + var task = TaskEx.Run(() => _eventSession.Source.Process()); +#endif + doAction(); + + task.Wait(); + + try + { + assertAction(valuesFromEvent); + } + finally + { + _eventSession.Source.Dynamic.All -= eventDelegate; + } + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/Log4NetTracingInterceptorTest.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/Log4NetTracingInterceptorTest.cs new file mode 100644 index 0000000000000..4e56f21dbb18b --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/Log4NetTracingInterceptorTest.cs @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using Microsoft.Rest.Tracing.Log4Net; +using Xunit; + +namespace Microsoft.Rest.ClientRuntime.Tracing.Tests +{ + public class Log4NetTracingInterceptorTest + { + private const string logFileName = "log-file.txt"; + + [Fact] + public void LogsConfiguration() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string expected = "DEBUG - Configuration: source=sourceName, name=Name, value=Value\r\n"; + logger.Configuration("sourceName", "Name", "Value"); + Assert.Equal(expected, File.ReadAllText(logFileName)); + } + + [Fact] + public void LogsInformation() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string infoMessage = "This is expected message"; + string expected = string.Format("INFO - {0}\r\n", infoMessage); + logger.Information(infoMessage); + Assert.Equal(expected, File.ReadAllText(logFileName)); + } + + [Fact] + public void LogsEnter() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string invocationId = "1234"; + object instance = "I'm an object"; + string method = "getData"; + IDictionary parameters = new Dictionary(); + string parametersLog = "{}"; + string expected = + string.Format("DEBUG - invocationId: {0}\r\ninstance: {1}\r\nmethod: {2}\r\nparameters: {3}\r\n", + invocationId, instance, method, parametersLog); + logger.EnterMethod(invocationId, instance, method, parameters); + Assert.Equal(expected, File.ReadAllText(logFileName)); + } + + [Fact] + public void LogsRequest() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string invocationId = "12345"; + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://azuresdk.com"); + string expected = string.Format("DEBUG - invocationId: {0}\r\nrequest: {1}\r\n", invocationId, + request.AsFormattedString()); + logger.SendRequest(invocationId, request); + string actual = File.ReadAllText(logFileName); + Assert.Equal(expected, actual); + } + + [Fact] + public void LogsResponse() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string invocationId = "12345"; + HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Accepted); + string expected = string.Format("DEBUG - invocationId: {0}\r\nresponse: {1}\r\n", invocationId, + response.AsFormattedString()); + logger.ReceiveResponse(invocationId, response); + string actual = File.ReadAllText(logFileName); + Assert.Equal(expected, actual); + } + + [Fact] + public void LogsError() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string invocationId = "12345"; + var exception = new HttpOperationException("I'm a cloud exception!"); + string expected = string.Format("ERROR - invocationId: {0}\r\n{1}\r\n", invocationId, exception.ToString()); + logger.TraceError(invocationId, exception); + string actual = File.ReadAllText(logFileName); + Assert.Equal(expected, actual); + } + + [Fact] + public void LogsExit() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string invocationId = "12345"; + string returnValue = "bye bye!"; + string expected = string.Format("DEBUG - Exit with invocation id {0}, the return value is {1}\r\n", + invocationId, returnValue); + logger.ExitMethod(invocationId, returnValue); + string actual = File.ReadAllText(logFileName); + Assert.Equal(expected, actual); + } + + [Fact] + public void LogsNullConfiguration() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string expected = "DEBUG - Configuration: source=, name=, value=\r\n"; + logger.Configuration(null, null, null); + Assert.Equal(expected, File.ReadAllText(logFileName)); + } + + [Fact] + public void LogsNullInformation() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string infoMessage = null; + string expected = string.Format("INFO - {0}\r\n", infoMessage); + logger.Information(infoMessage); + Assert.Equal(expected, File.ReadAllText(logFileName)); + } + + [Fact] + public void LogsNullEnter() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string invocationId = null; + object instance = null; + string method = null; + IDictionary parameters = null; + string parametersLog = "{}"; + string expected = + string.Format("DEBUG - invocationId: {0}\r\ninstance: {1}\r\nmethod: {2}\r\nparameters: {3}\r\n", + invocationId, instance, method, parametersLog); + logger.EnterMethod(invocationId, instance, method, parameters); + Assert.Equal(expected, File.ReadAllText(logFileName)); + } + + [Fact] + public void LogsNullRequest() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string invocationId = null; + HttpRequestMessage request = null; + string expected = "DEBUG - invocationId: \r\nrequest: \r\n"; + logger.SendRequest(invocationId, request); + Assert.Equal(expected, File.ReadAllText(logFileName)); + } + + [Fact] + public void LogsNullResponse() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string invocationId = null; + HttpResponseMessage response = null; + string expected = "DEBUG - invocationId: \r\nresponse: \r\n"; + logger.ReceiveResponse(invocationId, response); + Assert.Equal(expected, File.ReadAllText(logFileName)); + } + + [Fact] + public void LogsNullError() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string invocationId = null; + string expected = string.Format("ERROR - invocationId: \r\n", invocationId, null); + logger.TraceError(invocationId, null); + string actual = File.ReadAllText(logFileName); + Assert.Equal(expected, actual); + } + + [Fact] + public void LogsNullExit() + { + Log4NetTracingInterceptor logger = new Log4NetTracingInterceptor("Microsoft.Rest.ClientRuntime.Tracing.Tests.dll.config"); + string invocationId = null; + string returnValue = null; + string expected = string.Format("DEBUG - Exit with invocation id {0}, the return value is {1}\r\n", + invocationId, returnValue); + logger.ExitMethod(invocationId, returnValue); + string actual = File.ReadAllText(logFileName); + Assert.Equal(expected, actual); + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/Microsoft.Rest.ClientRuntime.Tracing.Tests.xproj b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/Microsoft.Rest.ClientRuntime.Tracing.Tests.xproj new file mode 100644 index 0000000000000..f0ba875b6dcd4 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/Microsoft.Rest.ClientRuntime.Tracing.Tests.xproj @@ -0,0 +1,19 @@ + + + + ..\..\..\ + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 52c61f15-bf86-41dc-93d1-05d3da70f032 + Microsoft.Rest.ClientRuntime.Tracing.Tests + .\obj + .\bin\ + + + 2.0 + + + \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/Properties/AssemblyInfo.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000..9fdd6b183c296 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using System.Resources; + +[assembly: AssemblyTitle("Microsoft Rest Client Runtime Tracing Tests")] +[assembly: AssemblyDescription("Tests for the Microsoft Rest Client Runtime Tracing.")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.4.2.0")] + +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Microsoft AutoRest Client Runtime Tests")] +[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/app.config b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/app.config new file mode 100644 index 0000000000000..310436eb2a805 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/app.config @@ -0,0 +1,26 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/project.json b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/project.json new file mode 100644 index 0000000000000..40426ea456f69 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime.Tracing.Tests/project.json @@ -0,0 +1,32 @@ +{ + "version": "2.0.0-preview", + "authors": [ "Microsoft" ], + + "packOptions": { + "summary": "ClientRuntime Tests.", + "tags": [ "Microsoft AutoRest ClientRuntime REST" ], + "projectUrl": "https://github.com/Azure/AutoRest", + "licenseUrl": "https://raw.githubusercontent.com/Microsoft/dotnet/master/LICENSE", + }, + + "testRunner": "xunit", + "frameworks": { + "net45": { + "imports": ["net451","dnxcore50"], + "frameworkAssemblies": { + "System": "", + "System.Runtime": "", + "System.Threading": "" + } + } + }, + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "Microsoft.Diagnostics.Tracing.TraceEvent": "1.0.41", + "Microsoft.Rest.ClientRuntime.Log4Net": "[2.1.1-preview,3.0.0)", + "Microsoft.Rest.ClientRuntime.Etw": "[2.1.1-preview,3.0.0)", + "Newtonsoft.Json": "6.0.8", + "xunit": "2.2.0-beta2-build3300", + "dotnet-test-xunit": "2.2.0-preview2-build1029" + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/BasicAuthenticationCredentials.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/BasicAuthenticationCredentials.cs new file mode 100644 index 0000000000000..55742e598ebef --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/BasicAuthenticationCredentials.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Rest +{ + /// + /// Basic Auth credentials for use with a REST Service Client. + /// + public class BasicAuthenticationCredentials : ServiceClientCredentials + { + /// + /// Basic auth UserName. + /// + public string UserName { get; set; } + + /// + /// Basic auth password. + /// + public string Password { get; set; } + + /// + /// Add the Basic Authentication Header to each outgoing request + /// + /// The outgoing request + /// A token to cancel the operation + /// + public override Task ProcessHttpRequestAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + if (request == null) + { + throw new ArgumentNullException("request"); + } + + // Add username and password to "Basic" header of each request. + request.Headers.Authorization = new AuthenticationHeaderValue("Basic", + Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format( + CultureInfo.InvariantCulture, + "{0}:{1}", + UserName, + Password).ToCharArray()))); + return Task.FromResult(null); + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/CertificateCredentials.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/CertificateCredentials.cs new file mode 100644 index 0000000000000..7963b82068e1a --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/CertificateCredentials.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +#if !PORTABLE + +using System; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Rest.ClientRuntime.Properties; + +namespace Microsoft.Rest +{ + /// + /// Certificate based credentials for use with a REST Service Client. + /// + public class CertificateCredentials : ServiceClientCredentials + { + /// + /// The Microsoft Azure Service Management API use mutual authentication + /// of management certificates over SSL to ensure that a request made + /// to the service is secure. No anonymous requests are allowed. + /// + public X509Certificate2 ManagementCertificate { get; private set; } + + /// + /// Initializes a new instance of the + /// class with the given 'Bearer' token. + /// + public CertificateCredentials(X509Certificate2 managementCertificate) + { + if (managementCertificate == null) + { + throw new ArgumentNullException("managementCertificate"); + } + ManagementCertificate = managementCertificate; + } + + /// + /// Initialize a ServiceClient instance to process credentials. + /// + /// Type of ServiceClient. + /// The ServiceClient. + /// + /// This will add a certificate to the shared root WebRequestHandler in + /// the ServiceClient's HttpClient handler pipeline. + /// + public override void InitializeServiceClient(ServiceClient client) + { + if (client == null) + { + throw new ArgumentNullException("client"); + } + + WebRequestHandler handler = client.HttpMessageHandlers.FirstOrDefault(h => h is WebRequestHandler) as WebRequestHandler; + if (handler == null) + { + throw new PlatformNotSupportedException( + string.Format( + CultureInfo.InvariantCulture, + Resources.WebRequestHandlerNotFound, + client.GetType().Name, + typeof(WebRequestHandler).Name)); + } + + handler.ClientCertificates.Add(ManagementCertificate); + } + } +} +#endif \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/GlobalSuppressions.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/GlobalSuppressions.cs new file mode 100644 index 0000000000000..3cb55c316fba3 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/GlobalSuppressions.cs @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", + Scope = "namespace", Target = "Microsoft.Rest.Serialization", Justification = "Json specific converters should not pollute base namespace.")] \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpExtensions.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpExtensions.cs new file mode 100644 index 0000000000000..4b97d64c41d4e --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpExtensions.cs @@ -0,0 +1,226 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Rest +{ + /// + /// Extensions for manipulating HTTP request and response objects. + /// + public static class HttpExtensions + { + /// + /// Formats an HttpContent object as String. + /// + /// The HttpContent to format. + /// The formatted string. + public static string AsString(this HttpContent content) + { + if (content != null) + { + // Await for the content. + return + content + .ReadAsStringAsync() + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(); + } + + return null; + } + + /// + /// Get the content headers of an HtttRequestMessage. + /// + /// The request message. + /// The content headers. + public static HttpHeaders GetContentHeaders(this HttpRequestMessage request) + { + if (request != null && request.Content != null) + { + return request.Content.Headers; + } + + return null; + } + + /// + /// Get the content headers of an HttpResponseMessage. + /// + /// The response message. + /// The content headers. + public static HttpHeaders GetContentHeaders(this HttpResponseMessage response) + { + if (response != null && response.Content != null) + { + return response.Content.Headers; + } + + return null; + } + + /// + /// Returns string representation of a HttpRequestMessage. + /// + /// Request object to format. + /// The string, formatted into curly braces. + public static string AsFormattedString(this HttpRequestMessage httpRequest) + { + if (httpRequest == null) + { + throw new ArgumentNullException("httpRequest"); + } + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine(httpRequest.ToString()); + if (httpRequest.Content != null) + { + stringBuilder.AppendLine(); + stringBuilder.AppendLine("Body:"); + stringBuilder.AppendLine("{"); + stringBuilder.AppendLine(httpRequest.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult()); + stringBuilder.AppendLine("}"); + } + return stringBuilder.ToString(); + } + + /// + /// Returns string representation of a HttpResponseMessage. + /// + /// Response object to format. + /// The string, formatted into curly braces. + public static string AsFormattedString(this HttpResponseMessage httpResponse) + { + if (httpResponse == null) + { + throw new ArgumentNullException("httpResponse"); + } + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine(httpResponse.ToString()); + if (httpResponse.Content != null) + { + stringBuilder.AppendLine(); + stringBuilder.AppendLine("Body:"); + stringBuilder.AppendLine("{"); + stringBuilder.AppendLine(httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult()); + stringBuilder.AppendLine("}"); + } + return stringBuilder.ToString(); + } + + /// + /// Converts given dictionary into a log string. + /// + /// The dictionary key type. + /// The dictionary value type. + /// The dictionary object. + /// The string, formatted into curly braces. + public static string AsFormattedString(this IDictionary dictionary) + { + if (dictionary == null) + { + return "{}"; + } + + return "{" + string.Join(",", + dictionary.Select(kv => kv.Key.ToString() + + "=" + + (kv.Value == null ? string.Empty : kv.Value.ToString())) + .ToArray()) + "}"; + } + + /// + /// Serializes HttpHeaders as Json dictionary. + /// + /// HttpHeaders + /// Json string + public static JObject ToJson(this HttpHeaders headers) + { + if (headers == null || !headers.Any()) + { + return new JObject(); + } + else + { + return headers.ToDictionary(h => h.Key, h => h.Value).ToJson(); + } + } + + /// + /// Serializes header dictionary as Json dictionary. + /// + /// Dictionary + /// Json string + public static JObject ToJson(this IDictionary> headers) + { + if (headers == null || !headers.Any()) + { + return new JObject(); + } + else + { + var jObject = new JObject(); + foreach (var httpResponseHeader in headers) + { + if (httpResponseHeader.Value.Count() > 1) + { + jObject[httpResponseHeader.Key] = new JArray(httpResponseHeader.Value); + } + else + { + jObject[httpResponseHeader.Key] = httpResponseHeader.Value.FirstOrDefault(); + } + } + return jObject; + } + } + + /// + /// Serializes HttpResponseHeaders and HttpContentHeaders as Json dictionary. + /// + /// HttpResponseMessage + /// Json string + public static JObject GetHeadersAsJson(this HttpResponseMessage message) + { + if (message == null) + { + return new JObject(); + } + + var jObject = new JObject(); + foreach (var httpResponseHeader in message.Headers) + { + if (httpResponseHeader.Value.Count() > 1) + { + jObject[httpResponseHeader.Key] = new JArray(httpResponseHeader.Value); + } + else + { + jObject[httpResponseHeader.Key] = httpResponseHeader.Value.FirstOrDefault(); + } + } + if (message.Content != null) + { + foreach (var httpResponseHeader in message.Content.Headers) + { + if (httpResponseHeader.Value.Count() > 1) + { + jObject[httpResponseHeader.Key] = new JArray(httpResponseHeader.Value); + } + else + { + jObject[httpResponseHeader.Key] = httpResponseHeader.Value.FirstOrDefault(); + } + } + } + return jObject; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpMessageWrapper.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpMessageWrapper.cs new file mode 100644 index 0000000000000..60f74b7470173 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpMessageWrapper.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; + +namespace Microsoft.Rest +{ + /// + /// Base class used to wrap HTTP requests and responses to preserve data after disposal of + /// HttpClient. + /// + public abstract class HttpMessageWrapper + { + /// + /// Initializes a new instance of the HttpMessageWrapper class. + /// + protected HttpMessageWrapper() + { + Headers = new Dictionary>(); + } + + /// + /// Exposes the HTTP message contents. + /// + public string Content { get; set; } + + /// + /// Gets the collection of HTTP headers. + /// + public IDictionary> Headers { get; private set; } + + /// + /// Copies HTTP message headers to the error object. + /// + /// Collection of HTTP headers. + protected void CopyHeaders(HttpHeaders headers) + { + if (headers != null) + { + foreach (KeyValuePair> header in headers) + { + IEnumerable values = null; + if (Headers.TryGetValue(header.Key, out values)) + { + values = Enumerable.Concat(values, header.Value); + } + else + { + values = header.Value; + } + Headers[header.Key] = values; + } + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpOperationException.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpOperationException.cs new file mode 100644 index 0000000000000..a0be721ad2ea8 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpOperationException.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Runtime.Serialization; +#if !PORTABLE +using System.Security.Permissions; +#endif + +namespace Microsoft.Rest +{ + /// + /// Exception thrown for an invalid response with custom error information. + /// +#if !PORTABLE + [Serializable] +#endif + public class HttpOperationException : RestException + { + /// + /// Gets information about the associated HTTP request. + /// + public HttpRequestMessageWrapper Request { get; set; } + + /// + /// Gets information about the associated HTTP response. + /// + public HttpResponseMessageWrapper Response { get; set; } + + /// + /// Gets or sets the response object. + /// + public object Body { get; set; } + + /// + /// Initializes a new instance of the HttpOperationException class. + /// + public HttpOperationException() + { + } + + /// + /// Initializes a new instance of the HttpOperationException class. + /// + /// The exception message. + public HttpOperationException(string message) + : this(message, null) + { + } + + /// + /// Initializes a new instance of the HttpOperationException class. + /// + /// The exception message. + /// Inner exception. + public HttpOperationException(string message, Exception innerException) + : base(message, innerException) + { + } + +#if !PORTABLE + /// + /// Initializes a new instance of the HttpOperationException class. + /// + /// Serialization info. + /// Streaming context. + protected HttpOperationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Serializes content of the exception. + /// + /// Serialization info. + /// Streaming context. + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + if (info == null) + { + throw new ArgumentNullException("info"); + } + + info.AddValue("Request", Request); + info.AddValue("Response", Response); + info.AddValue("Body", Body); + } +#endif + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpOperationResponse.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpOperationResponse.cs new file mode 100644 index 0000000000000..49d03b91d35e1 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpOperationResponse.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; + +namespace Microsoft.Rest +{ + /// + /// Represents the base return type of all ServiceClient REST operations without response body. + /// + public interface IHttpOperationResponse + { + /// + /// Gets information about the associated HTTP request. + /// + HttpRequestMessage Request { get; set; } + + /// + /// Gets information about the associated HTTP response. + /// + HttpResponseMessage Response { get; set; } + } + + /// + /// Represents the base return type of all ServiceClient REST operations with response body. + /// + public interface IHttpOperationResponse : IHttpOperationResponse + { + /// + /// Gets or sets the response object. + /// + T Body { get; set; } + } + + /// + /// Represents the base return type of all ServiceClient REST operations with a header response. + /// + /// + public interface IHttpOperationHeaderResponse : IHttpOperationResponse + { + /// + /// Gets or sets the response header object. + /// + T Headers { get; set; } + } + + /// + /// Represents the base return type of all ServiceClient REST operations with response body and header. + /// + public interface IHttpOperationResponse : IHttpOperationResponse, IHttpOperationHeaderResponse + { + + } + + /// + /// Represents the base return type of all ServiceClient REST operations without response body. + /// + public class HttpOperationResponse : IHttpOperationResponse, IDisposable + { + /// + /// Indicates whether the HttpOperationResponse has been disposed. + /// + private bool _disposed; + + /// + /// Gets information about the associated HTTP request. + /// + public HttpRequestMessage Request { get; set; } + + /// + /// Gets information about the associated HTTP response. + /// + public HttpResponseMessage Response { get; set; } + + /// + /// Dispose the HttpOperationResponse. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose the HttpClient and Handlers. + /// + /// True to release both managed and unmanaged resources; false to releases only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + _disposed = true; + + // Dispose the request and response + if (Request != null) + { + Request.Dispose(); + } + if (Response != null) + { + Response.Dispose(); + } + Request = null; + Response = null; + } + } + } + + /// + /// Represents the base return type of all ServiceClient REST operations. + /// + public class HttpOperationResponse : HttpOperationResponse, IHttpOperationResponse + { + /// + /// Gets or sets the response object. + /// + public T Body { get; set; } + } + + /// + /// Represents the base return type of all ServiceClient REST operations. + /// + public class HttpOperationHeaderResponse : HttpOperationResponse, IHttpOperationHeaderResponse + { + public THeader Headers { get; set; } + } + + /// + /// Represents the base return type of all ServiceClient REST operations. + /// + public class HttpOperationResponse : HttpOperationResponse, IHttpOperationResponse + { + /// + /// Gets or sets the response header object. + /// + public THeader Headers { get; set; } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpRequestMessageWrapper.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpRequestMessageWrapper.cs new file mode 100644 index 0000000000000..8252d364a67d3 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpRequestMessageWrapper.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace Microsoft.Rest +{ + /// + /// Wrapper around HttpRequestMessage type that copies properties of HttpRequestMessage so that + /// they are available after the HttpClient gets disposed. + /// + public class HttpRequestMessageWrapper : HttpMessageWrapper + { + /// + /// Initializes a new instance of the HttpRequestMessageWrapper class from HttpRequestMessage + /// and content. + /// + public HttpRequestMessageWrapper(HttpRequestMessage httpRequest, string content) + { + if (httpRequest == null) + { + throw new ArgumentNullException("httpRequest"); + } + + this.CopyHeaders(httpRequest.Headers); + this.CopyHeaders(httpRequest.GetContentHeaders()); + + this.Content = content; + this.Method = httpRequest.Method; + this.RequestUri = httpRequest.RequestUri; + if (httpRequest.Properties != null) + { + Properties = new Dictionary(); + foreach (KeyValuePair pair in httpRequest.Properties) + { + this.Properties[pair.Key] = pair.Value; + } + } + } + + /// + /// Gets or sets the HTTP method used by the HTTP request message. + /// + public HttpMethod Method { get; protected set; } + + /// + /// Gets or sets the Uri used for the HTTP request. + /// + public Uri RequestUri { get; protected set; } + + /// + /// Gets a set of properties for the HTTP request. + /// + public IDictionary Properties { get; private set; } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpResponseMessageWrapper.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpResponseMessageWrapper.cs new file mode 100644 index 0000000000000..11abdfdccba60 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/HttpResponseMessageWrapper.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Net.Http; + +namespace Microsoft.Rest +{ + /// + /// Wrapper around HttpResponseMessage type that copies properties of HttpResponseMessage so that + /// they are available after the HttpClient gets disposed. + /// + public class HttpResponseMessageWrapper : HttpMessageWrapper + { + /// + /// Initializes a new instance of the HttpResponseMessageWrapper class from HttpResponseMessage + /// and content. + /// + public HttpResponseMessageWrapper(HttpResponseMessage httpResponse, string content) + { + if (httpResponse == null) + { + throw new ArgumentNullException("httpResponse"); + } + + this.CopyHeaders(httpResponse.Headers); + this.CopyHeaders(httpResponse.GetContentHeaders()); + + this.Content = content; + this.StatusCode = httpResponse.StatusCode; + this.ReasonPhrase = httpResponse.ReasonPhrase; + } + + /// + /// Gets or sets the status code of the HTTP response. + /// + public HttpStatusCode StatusCode { get; protected set; } + + /// + /// Exposes the reason phrase, typically sent along with the status code. + /// + public string ReasonPhrase { get; protected set; } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/IServiceClientTracingInterceptor.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/IServiceClientTracingInterceptor.cs new file mode 100644 index 0000000000000..15835e7b52aa5 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/IServiceClientTracingInterceptor.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace Microsoft.Rest +{ + /// + /// The IServiceClientTracingInterceptor provides useful information about cloud + /// operations. Interception is global and a tracing interceptor can be + /// added via TracingAdapter.AddTracingInterceptor. + /// + public interface IServiceClientTracingInterceptor + { + /// + /// Probe configuration for the value of a setting. + /// + /// The configuration source. + /// The name of the setting. + /// The value of the setting in the source. + void Configuration(string source, string name, string value); + + /// + /// Enter a method. + /// + /// Method invocation identifier. + /// The instance with the method. + /// Name of the method. + /// Method parameters. + void EnterMethod(string invocationId, object instance, string method, IDictionary parameters); + + /// + /// Exit a method. Note: Exit will not be called in the event of an + /// error. + /// + /// Method invocation identifier. + /// Method return value. + void ExitMethod(string invocationId, object returnValue); + + /// + /// Trace information. + /// + /// The information to trace. + void Information(string message); + + /// + /// Receive an HTTP response. + /// + /// Method invocation identifier. + /// The response instance. + void ReceiveResponse(string invocationId, HttpResponseMessage response); + + /// + /// Send an HTTP request. + /// + /// Method invocation identifier. + /// The request about to be sent. + void SendRequest(string invocationId, HttpRequestMessage request); + + /// + /// Raise an error. + /// + /// Method invocation identifier. + /// The error. + void TraceError(string invocationId, Exception exception); + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/IServiceOperations.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/IServiceOperations.cs new file mode 100644 index 0000000000000..3d066d6de807a --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/IServiceOperations.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Rest +{ + /// + /// Interface used to group operations of a ServiceClient. + /// + /// Type of the ServiceClient. + public interface IServiceOperations + where TClient : ServiceClient + { + /// + /// Gets a reference to the ServiceClient. + /// + TClient Client { get; } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ITokenProvider.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ITokenProvider.cs new file mode 100644 index 0000000000000..1d4b2965cc103 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ITokenProvider.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Rest +{ + /// + /// Interface to a source of access tokens. + /// + public interface ITokenProvider + { + /// + /// Gets the authentication header with token. + /// + Task GetAuthenticationHeaderAsync(CancellationToken cancellationToken); + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Microsoft.Rest.ClientRuntime.sln b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Microsoft.Rest.ClientRuntime.sln new file mode 100644 index 0000000000000..ac5bf7f5157a1 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Microsoft.Rest.ClientRuntime.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime", "Microsoft.Rest.ClientRuntime.xproj", "{EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Tests", "..\Microsoft.Rest.ClientRuntime.Tests\Microsoft.Rest.ClientRuntime.Tests.xproj", "{F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.Rest.ClientRuntime.Tracing.Tests", "..\Microsoft.Rest.ClientRuntime.Tracing.Tests\Microsoft.Rest.ClientRuntime.Tracing.Tests.xproj", "{52C61F15-BF86-41DC-93D1-05D3DA70F032}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDDB6367-5C7B-428C-B54C-96BCD90F6E6C}.Release|Any CPU.Build.0 = Release|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F7F20E35-43EE-4FCC-BF83-7BF93B192BC8}.Release|Any CPU.Build.0 = Release|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52C61F15-BF86-41DC-93D1-05D3DA70F032}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Microsoft.Rest.ClientRuntime.xproj b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Microsoft.Rest.ClientRuntime.xproj new file mode 100644 index 0000000000000..8614b5cc141c1 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Microsoft.Rest.ClientRuntime.xproj @@ -0,0 +1,23 @@ + + + + ..\..\..\ + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + eddb6367-5c7b-428c-b54c-96bcd90f6e6c + Microsoft.Rest.ClientRuntime + .\obj + .\bin\ + + + + 2.0 + + + True + + + diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Properties/AssemblyInfo.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000..96d747a5de5c0 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Microsoft Rest Client Runtime")] +[assembly: AssemblyDescription("Client infrastructure for client libraries generated by AutoRest.")] +[assembly: AssemblyVersion("2.0.0.0")] +[assembly: AssemblyFileVersion("2.3.2.0")] + +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft Corporation")] +[assembly: AssemblyProduct("Microsoft AutoRest")] +[assembly: AssemblyCopyright("Copyright (c) Microsoft Corporation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] +[assembly: CLSCompliant(false)] +[assembly: ComVisible(false)] diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Properties/Resources.Designer.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Properties/Resources.Designer.cs new file mode 100644 index 0000000000000..ae8754c4a8a39 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Properties/Resources.Designer.cs @@ -0,0 +1,323 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Rest.ClientRuntime.Properties { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Rest.ClientRuntime.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The specified argument {0} cannot be greater than its ceiling value of {1}.. + /// + internal static string ArgumentCannotBeGreaterThanBaseline { + get { + return ResourceManager.GetString("ArgumentCannotBeGreaterThanBaseline", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument {0} cannot be initialized with a negative value.. + /// + internal static string ArgumentCannotBeNegative { + get { + return ResourceManager.GetString("ArgumentCannotBeNegative", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Received unexpected Http response status code {0}. + /// + internal static string DefaultHttpOperationExceptionMessage { + get { + return ResourceManager.GetString("DefaultHttpOperationExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Default retry strategy for technology {0}, named '{1}', is not defined.. + /// + internal static string DefaultRetryStrategyMappingNotFound { + get { + return ResourceManager.GetString("DefaultRetryStrategyMappingNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Default retry strategy for technology {0} was not not defined, and there is no overall default strategy.. + /// + internal static string DefaultRetryStrategyNotFound { + get { + return ResourceManager.GetString("DefaultRetryStrategyNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Retry handler is not present in the HttpClient handler stack.. + /// + internal static string ExceptionRetryHandlerMissing { + get { + return ResourceManager.GetString("ExceptionRetryHandlerMissing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The RetryManager is already set.. + /// + internal static string ExceptionRetryManagerAlreadySet { + get { + return ResourceManager.GetString("ExceptionRetryManagerAlreadySet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The default RetryManager has not been set. Set it by invoking the RetryManager.SetDefault static method, or if you are using declarative configuration, you can invoke the RetryPolicyFactory.CreateDefault() method to automatically create the retry manager from the configuration file.. + /// + internal static string ExceptionRetryManagerNotSet { + get { + return ResourceManager.GetString("ExceptionRetryManagerNotSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The error detection strategy type must implement the ITransientErrorDetectionStrategy interface.. + /// + internal static string ITransientErrorDetectionStrategyNotImplemented { + get { + return ResourceManager.GetString("ITransientErrorDetectionStrategyNotImplemented", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Response status code indicates server error: {0} ({1}).. + /// + internal static string ResponseStatusCodeError { + get { + return ResourceManager.GetString("ResponseStatusCodeError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The retry strategy with name '{0}' cannot be found.. + /// + internal static string RetryStrategyNotFound { + get { + return ResourceManager.GetString("RetryStrategyNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified string argument {0} must not be empty.. + /// + internal static string StringCannotBeEmpty { + get { + return ResourceManager.GetString("StringCannotBeEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument '{0}' cannot return a null task when invoked.. + /// + internal static string TaskCannotBeNull { + get { + return ResourceManager.GetString("TaskCannotBeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The specified argument '{0}' must return a scheduled task (also known as "hot" task) when invoked.. + /// + internal static string TaskMustBeScheduled { + get { + return ResourceManager.GetString("TaskMustBeScheduled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You must specify a token provider or raw access token before using token credentials.. + /// + internal static string TokenProviderCannotBeNull { + get { + return ResourceManager.GetString("TokenProviderCannotBeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to cannot be null. + /// + internal static string ValidationCannotBeNull { + get { + return ResourceManager.GetString("ValidationCannotBeNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to cannot have value other than. + /// + internal static string ValidationEnum { + get { + return ResourceManager.GetString("ValidationEnum", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to is equal or exceeds maximum value of. + /// + internal static string ValidationExclusiveMaximum { + get { + return ResourceManager.GetString("ValidationExclusiveMaximum", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to is less than or equal minimum value of. + /// + internal static string ValidationExclusiveMinimum { + get { + return ResourceManager.GetString("ValidationExclusiveMinimum", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to exceeds maximum value of. + /// + internal static string ValidationMaximum { + get { + return ResourceManager.GetString("ValidationMaximum", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to exceeds maximum item count of. + /// + internal static string ValidationMaximumItems { + get { + return ResourceManager.GetString("ValidationMaximumItems", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to exceeds maximum length of. + /// + internal static string ValidationMaximumLength { + get { + return ResourceManager.GetString("ValidationMaximumLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to is less than minimum value of. + /// + internal static string ValidationMinimum { + get { + return ResourceManager.GetString("ValidationMinimum", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to contains less items than. + /// + internal static string ValidationMinimumItems { + get { + return ResourceManager.GetString("ValidationMinimumItems", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to is less than minimum length of. + /// + internal static string ValidationMinimumLength { + get { + return ResourceManager.GetString("ValidationMinimumLength", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to has to be multiple of. + /// + internal static string ValidationMultipleOf { + get { + return ResourceManager.GetString("ValidationMultipleOf", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to does not match expected pattern. + /// + internal static string ValidationPattern { + get { + return ResourceManager.GetString("ValidationPattern", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to collection contains duplicate items. + /// + internal static string ValidationUniqueItems { + get { + return ResourceManager.GetString("ValidationUniqueItems", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The service client '{0}' did not contain an implementation of '{1}'.. + /// + internal static string WebRequestHandlerNotFound { + get { + return ResourceManager.GetString("WebRequestHandlerNotFound", resourceCulture); + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Properties/Resources.resx b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Properties/Resources.resx new file mode 100644 index 0000000000000..b5d549580fb6e --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Properties/Resources.resx @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The specified argument {0} cannot be greater than its ceiling value of {1}. + + + The specified argument {0} cannot be initialized with a negative value. + + + Received unexpected Http response status code {0} + + + Default retry strategy for technology {0}, named '{1}', is not defined. + + + Default retry strategy for technology {0} was not not defined, and there is no overall default strategy. + + + Retry handler is not present in the HttpClient handler stack. + + + The RetryManager is already set. + + + The default RetryManager has not been set. Set it by invoking the RetryManager.SetDefault static method, or if you are using declarative configuration, you can invoke the RetryPolicyFactory.CreateDefault() method to automatically create the retry manager from the configuration file. + + + The error detection strategy type must implement the ITransientErrorDetectionStrategy interface. + + + Response status code indicates server error: {0} ({1}). + + + The retry strategy with name '{0}' cannot be found. + + + The specified string argument {0} must not be empty. + + + The specified argument '{0}' cannot return a null task when invoked. + + + The specified argument '{0}' must return a scheduled task (also known as "hot" task) when invoked. + + + You must specify a token provider or raw access token before using token credentials. + + + cannot be null + + + cannot have value other than + + + is equal or exceeds maximum value of + + + is less than or equal minimum value of + + + exceeds maximum value of + + + exceeds maximum item count of + + + exceeds maximum length of + + + is less than minimum value of + + + contains less items than + + + is less than minimum length of + + + has to be multiple of + + + does not match expected pattern + + + collection contains duplicate items + + + The service client '{0}' did not contain an implementation of '{1}'. + + \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/RestException.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/RestException.cs new file mode 100644 index 0000000000000..edd67d49f327e --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/RestException.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Runtime.Serialization; + +namespace Microsoft.Rest +{ + /// + /// Generic exception for Microsoft Rest Client. + /// +#if !PORTABLE + [Serializable] +#endif + public class RestException : Exception + { + /// + /// Initializes a new instance of the RestException class. + /// + public RestException() + { + } + + /// + /// Initializes a new instance of the RestException class. + /// + /// The exception message. + public RestException(string message) + : this(message, null) + { + } + + /// + /// Initializes a new instance of the RestException class. + /// + /// The exception message. + /// Inner exception. + public RestException(string message, Exception innerException) + : base(message, innerException) + { + } + +#if !PORTABLE + /// + /// Initializes a new instance of the RestException class. + /// + /// Serialization info. + /// Streaming context. + protected RestException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +#endif + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/RetryDelegatingHandler.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/RetryDelegatingHandler.cs new file mode 100644 index 0000000000000..a1fd09ae65bd3 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/RetryDelegatingHandler.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Rest.ClientRuntime.Properties; +using Microsoft.Rest.TransientFaultHandling; + +namespace Microsoft.Rest +{ + /// + /// Http retry handler. + /// + public class RetryDelegatingHandler : DelegatingHandler + { + private const int DefaultNumberOfAttempts = 3; + private readonly TimeSpan DefaultBackoffDelta = new TimeSpan(0, 0, 10); + private readonly TimeSpan DefaultMaxBackoff = new TimeSpan(0, 0, 10); + private readonly TimeSpan DefaultMinBackoff = new TimeSpan(0, 0, 1); + + /// + /// Initializes a new instance of the class. + /// Sets default retry policy base on Exponential Backoff. + /// + public RetryDelegatingHandler() + { + var retryStrategy = new ExponentialBackoffRetryStrategy( + DefaultNumberOfAttempts, + DefaultMinBackoff, + DefaultMaxBackoff, + DefaultBackoffDelta); + RetryPolicy = new RetryPolicy(retryStrategy); + } + + /// + /// Initializes a new instance of the class. Sets + /// the default retry policy base on Exponential Backoff. + /// + /// Inner http handler. + public RetryDelegatingHandler(DelegatingHandler innerHandler) + : base(innerHandler) + { + var retryStrategy = new ExponentialBackoffRetryStrategy( + DefaultNumberOfAttempts, + DefaultMinBackoff, + DefaultMaxBackoff, + DefaultBackoffDelta); + RetryPolicy = new RetryPolicy(retryStrategy); + } + + /// + /// Initializes a new instance of the class. + /// + /// Retry policy to use. + /// Inner http handler. + public RetryDelegatingHandler(RetryPolicy retryPolicy, DelegatingHandler innerHandler) + : base(innerHandler) + { + if (retryPolicy == null) + { + throw new ArgumentNullException("retryPolicy"); + } + RetryPolicy = retryPolicy; + } + + /// + /// Gets or sets retry policy. + /// + public RetryPolicy RetryPolicy { get; set; } + + /// + /// Sends an HTTP request to the inner handler to send to the server as an asynchronous + /// operation. Retries request if needed based on Retry Policy. + /// + /// The HTTP request message to send to the server. + /// A cancellation token to cancel operation. + /// Returns System.Threading.Tasks.Task<TResult>. The + /// task object representing the asynchronous operation. + protected override async Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + RetryPolicy.Retrying += (sender, args) => + { + if (Retrying != null) + { + Retrying(sender, args); + } + }; + + HttpResponseMessage responseMessage = null; + try + { + await RetryPolicy.ExecuteAsync(async () => + { + responseMessage = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); + + if (!responseMessage.IsSuccessStatusCode) + { + // dispose the message unless we have stopped retrying + this.Retrying += (sender, args) => + { + if (responseMessage != null) + { + responseMessage.Dispose(); + responseMessage = null; + } + }; + + throw new HttpRequestWithStatusException(string.Format( + CultureInfo.InvariantCulture, + Resources.ResponseStatusCodeError, + (int) responseMessage.StatusCode, + responseMessage.StatusCode)) {StatusCode = responseMessage.StatusCode}; + } + + return responseMessage; + }, cancellationToken).ConfigureAwait(false); + + return responseMessage; + } + catch + { + if (responseMessage != null) + { + return responseMessage; + } + else + { + throw; + } + } + finally + { + if (Retrying != null) + { + foreach (EventHandler d in Retrying.GetInvocationList()) + { + Retrying -= d; + } + } + } + } + + /// + /// An instance of a callback delegate that will be invoked whenever a retry condition is encountered. + /// + private event EventHandler Retrying; + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/Base64UrlJsonConverter.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/Base64UrlJsonConverter.cs new file mode 100644 index 0000000000000..3120ec92c4841 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/Base64UrlJsonConverter.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Rest.Serialization +{ + public class Base64UrlJsonConverter : JsonConverter + { + /// + /// Converts a byte array to a Base64Url encoded string + /// + /// The byte array to convert + /// The Base64Url encoded form of the input + private static string ToBase64UrlString(byte[] input) + { + if (input == null) + throw new ArgumentNullException("input"); + + return Convert.ToBase64String(input).TrimEnd('=').Replace('+', '-').Replace('/', '_'); + } + + /// + /// Converts a Base64Url encoded string to a byte array + /// + /// The Base64Url encoded string + /// The byte array represented by the enconded string + private static byte[] FromBase64UrlString(string input) + { + if (string.IsNullOrEmpty(input)) + throw new ArgumentNullException("input"); + + return Convert.FromBase64String(Pad(input.Replace('-', '+').Replace('_', '/'))); + } + + /// + /// Adds padding to the input + /// + /// the input string + /// the padded string + private static string Pad(string input) + { + var count = 3 - ((input.Length + 3) % 4); + + if (count == 0) + { + return input; + } + + return input + new string('=', count); + } + + public override bool CanConvert(Type objectType) + { + if (objectType == typeof(byte[])) + return true; + + return false; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (objectType != typeof(byte[])) + { + return serializer.Deserialize(reader, objectType); + } + else + { + var value = serializer.Deserialize(reader); + + if (!string.IsNullOrEmpty(value)) + { + return FromBase64UrlString(value); + } + } + + return null; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value.GetType() != typeof(byte[])) + { + JToken.FromObject(value).WriteTo(writer); + } + else + { + JToken.FromObject(ToBase64UrlString((byte[])value)).WriteTo(writer); + } + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/DateJsonConverter.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/DateJsonConverter.cs new file mode 100644 index 0000000000000..382b9a9191a46 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/DateJsonConverter.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Microsoft.Rest.Serialization +{ + /// + /// JsonConverter that handles serialization for dates in yyyy-MM-dd format. + /// + public class DateJsonConverter : IsoDateTimeConverter + { + /// + /// Initializes a new instance of DateJsonConverter. + /// + public DateJsonConverter() + { + DateTimeFormat = "yyyy-MM-dd"; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + try + { + return base.ReadJson(reader, objectType, existingValue, serializer); + } + catch (FormatException ex) + { + throw new JsonException("Unable to deserialize a Date.", ex); + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + try + { + base.WriteJson(writer, value, serializer); + } + catch (FormatException ex) + { + throw new JsonException("Unable to serialize a Date.", ex); + } + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/DateTimeRfc1123JsonConverter.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/DateTimeRfc1123JsonConverter.cs new file mode 100644 index 0000000000000..89042d7052559 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/DateTimeRfc1123JsonConverter.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Rest.Serialization +{ + using System; + using System.Diagnostics.CodeAnalysis; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + /// + /// JsonConverter that handles serialization for dates in RFC1123 format. + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Rfc", Justification = "Rfc is correct spelling")] + public class DateTimeRfc1123JsonConverter : IsoDateTimeConverter + { + /// + /// Initializes a new instance of DateJsonConverter. + /// + public DateTimeRfc1123JsonConverter() + { + this.DateTimeFormat = "R"; + } + + //TODO: This method can be removed if we used DateTimeOffsets instead + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + try + { + object o = base.ReadJson(reader, objectType, existingValue, serializer); + + //This is required because DateTime is really bad at parsing RFC1123 dates in C#. Basically it parses the value but + //doesn't attach a DateTimeKind to it (even though RFC1123 specification says that the "Kind" should be UTC. This results + //in a DateTime WITHOUT a DateTimeKind specifier, which is bad bad bad. We do the below in order to force the DateTimeKind + //of the resulting DateTime to be DateTimeKind.Utc since that is what the RFC1123 specification implies. + //See: http://stackoverflow.com/questions/1201378/how-does-datetime-touniversaltime-work + //See: http://stackoverflow.com/questions/16442484/datetime-unspecified-kind + DateTime? time = o as DateTime?; + + if (time.HasValue && time.Value.Kind == DateTimeKind.Unspecified) + { + time = DateTime.SpecifyKind(time.Value, DateTimeKind.Utc); + } + + return time; + } + catch (FormatException ex) + { + throw new JsonException("Unable to deserialize a Date.", ex); + } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + try + { + base.WriteJson(writer, value, serializer); + } + catch (FormatException ex) + { + throw new JsonException("Unable to serialize a Date.", ex); + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/Iso8601TimeSpanConverter.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/Iso8601TimeSpanConverter.cs new file mode 100644 index 0000000000000..4469aed2cf51c --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/Iso8601TimeSpanConverter.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +namespace Microsoft.Rest.Serialization +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Xml; + using Newtonsoft.Json; + + /// + /// Converter used to convert timespan to ISO8601 format + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Iso", Justification = "Iso is correct spelling")] + public class Iso8601TimeSpanConverter : JsonConverter + { + /// + /// Writes the specified object to JSON. + /// + /// The JSON writer. + /// The value to serialize. + /// The JSON serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (serializer == null) + { + throw new ArgumentNullException("serializer"); + } + + //TODO: Will only be called with values of TimeSpan or TimeSpan? (Do we have to handle Nullable?) + TimeSpan timeSpan = (TimeSpan)value; + string iso8601TimeSpanString = XmlConvert.ToString(timeSpan); //XmlConvert for TimeSpan uses ISO8601, so delegate serialization to it + serializer.Serialize(writer, iso8601TimeSpanString); + } + + /// + /// Reads the JSON token. + /// + /// The JSON reader. + /// The object type. + /// The existing value of object being read. + /// The JSON serializer. + /// The object value. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader == null) + { + throw new ArgumentNullException("reader"); + } + if (serializer == null) + { + throw new ArgumentNullException("serializer"); + } + + if (reader.TokenType == JsonToken.Null) + { + return null; + } + + string str = serializer.Deserialize(reader); + TimeSpan timeSpan = XmlConvert.ToTimeSpan(str); + return timeSpan; + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// true if this instance can convert the specified object type; otherwise, false. + public override bool CanConvert(Type objectType) + { + return objectType == typeof(TimeSpan) || objectType == typeof(TimeSpan?); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/JsonConverterHelper.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/JsonConverterHelper.cs new file mode 100644 index 0000000000000..cc455e43d47ba --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/JsonConverterHelper.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +#if!NET45 +using System.Reflection; +#endif + + +namespace Microsoft.Rest.Serialization +{ + /// + /// Helper class for JsonConverters. + /// + public static class JsonConverterHelper + { + /// + /// Serializes properties of the value object into JsonWriter. + /// + /// The JSON writer. + /// The value to serialize. + /// The JSON serializer. + public static void SerializeProperties(JsonWriter writer, object value, JsonSerializer serializer) + { + SerializeProperties(writer, value, serializer, null); + } + + /// + /// Serializes properties of the value object into JsonWriter. + /// + /// The JSON writer. + /// The value to serialize. + /// The JSON serializer. + /// If specified filters JsonProperties to be serialized. + public static void SerializeProperties(JsonWriter writer, object value, JsonSerializer serializer, + Predicate filter) + { + if (writer == null) + { + throw new ArgumentNullException("writer"); + } + if (value == null) + { + throw new ArgumentNullException("value"); + } + if (serializer == null) + { + throw new ArgumentNullException("serializer"); + } + + var contract = (JsonObjectContract) serializer.ContractResolver.ResolveContract(value.GetType()); + foreach (JsonProperty property in contract.Properties + .Where(p => filter == null || filter(p))) + { + object memberValue = property.ValueProvider.GetValue(value); + + // Skipping properties with null value if NullValueHandling is set to Ignore + if (serializer.NullValueHandling == NullValueHandling.Ignore && + memberValue == null) + { + continue; + } + + // Skipping properties with JsonIgnore attribute, non-readable, and + // ShouldSerialize returning false when set + if (!property.Ignored && property.Readable && + (property.ShouldSerialize == null || property.ShouldSerialize(memberValue))) + { + string propertyName = property.PropertyName; + if (property.PropertyName.StartsWith("properties.", StringComparison.OrdinalIgnoreCase)) + { + propertyName = property.PropertyName.Substring("properties.".Length); + } + writer.WritePropertyName(propertyName); + serializer.Serialize(writer, memberValue); + } + } + } + + public static string GetPropertyName(this JsonProperty property, out string[] parentPath) + { + if (property == null) + { + throw new ArgumentNullException("property"); + } + + string propertyName = property.PropertyName; + parentPath = new string[0]; + + if (!string.IsNullOrEmpty(propertyName)) + { + string[] hierarchy = Regex.Split(propertyName, @"(? p?.Replace("\\.", ".")).ToArray(); + if (hierarchy.Length > 1) + { + propertyName = hierarchy.Last(); + parentPath = hierarchy.Take(hierarchy.Length - 1).ToArray(); + } + } + + return propertyName; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/JsonTransformationAttribute.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/JsonTransformationAttribute.cs new file mode 100644 index 0000000000000..f40898e62890d --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/JsonTransformationAttribute.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Rest.Serialization +{ + /// + /// Instructs the Microsoft.Rest.Serialization.TransformationJsonConverter to + /// transform properties of the type based on dot convention. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class JsonTransformationAttribute : Attribute + { + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/PolymorphicDeserializeJsonConverter.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/PolymorphicDeserializeJsonConverter.cs new file mode 100644 index 0000000000000..e40edd73fb83f --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/PolymorphicDeserializeJsonConverter.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Rest.Serialization +{ + /// + /// JsonConverter that handles deserialization for polymorphic objects + /// based on discriminator field. + /// + /// The base type. + public class PolymorphicDeserializeJsonConverter : PolymorphicJsonConverter where T : class + { + /// + /// Initializes an instance of the PolymorphicDeserializeJsonConverter. + /// + /// The JSON field used as a discriminator + public PolymorphicDeserializeJsonConverter(string discriminatorField) + { + if (discriminatorField == null) + { + throw new ArgumentNullException("discriminatorField"); + } + Discriminator = discriminatorField; + } + + /// + /// Returns false. + /// + public override bool CanWrite + { + get { return false; } + } + + /// + /// Returns true if the object being deserialized is the base type. False otherwise. + /// + /// The type of the object to check. + /// True if the object being deserialized is the base type. False otherwise. + public override bool CanConvert(Type objectType) + { + return typeof (T) == objectType; + } + + /// + /// Reads a JSON field and deserializes into an appropriate object based on discriminator + /// field and object name. If JsonObject attribute is available, its value is used instead. + /// + /// The JSON reader. + /// The type of the object. + /// The existing value. + /// The JSON serializer. + /// + public override object ReadJson(JsonReader reader, + Type objectType, object existingValue, JsonSerializer serializer) + { + try + { + JObject item = JObject.Load(reader); + string typeDiscriminator = (string) item[Discriminator]; + Type derivedType = GetDerivedType(typeof (T), typeDiscriminator); + if (derivedType != null) + { + return item.ToObject(derivedType, serializer); + } + return item.ToObject(objectType); + } + catch (JsonException) + { + return null; + } + } + + /// + /// Throws NotSupportedException. + /// + /// The JSON writer. + /// The value to serialize. + /// The JSON serializer. + public override void WriteJson(JsonWriter writer, + object value, JsonSerializer serializer) + { + throw new NotSupportedException(); + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/PolymorphicJsonConverter.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/PolymorphicJsonConverter.cs new file mode 100644 index 0000000000000..aae83a26e921d --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/PolymorphicJsonConverter.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; + +namespace Microsoft.Rest.Serialization +{ + /// + /// Base JSON converter for polymorphic objects. + /// + public abstract class PolymorphicJsonConverter : JsonConverter + { + /// + /// Discriminator property name. + /// + public string Discriminator { get; protected set; } + + /// + /// Returns type that matches specified name. + /// + /// Base type. + /// Derived type name + /// + public static Type GetDerivedType(Type baseType, string name) + { + if (baseType == null) + { + throw new ArgumentNullException("baseType"); + } + foreach (TypeInfo type in baseType.GetTypeInfo().Assembly.DefinedTypes + .Where(t => t.Namespace == baseType.Namespace && t != baseType.GetTypeInfo() && t.IsSubclassOf(baseType))) + { + string typeName = type.Name; + if (type.GetCustomAttributes().Any()) + { + typeName = type.GetCustomAttribute().Id; + } + if (typeName != null && typeName.Equals(name, StringComparison.OrdinalIgnoreCase)) + { + return type.AsType(); + } + } + + return null; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/PolymorphicSerializeJsonConverter.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/PolymorphicSerializeJsonConverter.cs new file mode 100644 index 0000000000000..14887336c85fe --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/PolymorphicSerializeJsonConverter.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; + +namespace Microsoft.Rest.Serialization +{ + /// + /// JsonConverter that handles serialization for polymorphic objects + /// based on discriminator field. + /// + /// The base type. + public class PolymorphicSerializeJsonConverter : PolymorphicJsonConverter where T : class + { + /// + /// Initializes an instance of the PolymorphicSerializeJsonConverter. + /// + /// The JSON field used as a discriminator + public PolymorphicSerializeJsonConverter(string discriminatorField) + { + if (discriminatorField == null) + { + throw new ArgumentNullException("discriminatorField"); + } + Discriminator = discriminatorField; + } + + /// + /// Returns true if the object being serialized is assignable from the base type. False otherwise. + /// + /// The type of the object to check. + /// True if the object being serialized is assignable from the base type. False otherwise. + public override bool CanConvert(Type objectType) + { + return typeof(T).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); + } + + /// + /// Returns false. + /// + public override bool CanRead + { + get { return false; } + } + + /// + /// Throws NotSupportedException. + /// + /// The JSON reader. + /// The type of the object. + /// The existing value. + /// The JSON serializer. + /// + public override object ReadJson(JsonReader reader, + Type objectType, object existingValue, JsonSerializer serializer) + { + throw new NotSupportedException(); + } + + /// + /// Serializes an object into a JSON string based on discriminator + /// field and object name. If JsonObject attribute is available, its value is used instead. + /// + /// The JSON writer. + /// The value to serialize. + /// The JSON serializer. + public override void WriteJson(JsonWriter writer, + object value, JsonSerializer serializer) + { + if (writer == null) + { + throw new ArgumentNullException("writer"); + } + if (value == null) + { + throw new ArgumentNullException("value"); + } + if (serializer == null) + { + throw new ArgumentNullException("serializer"); + } + + string typeName = value.GetType().Name; + if (value.GetType().GetTypeInfo().GetCustomAttributes().Any()) + { + typeName = value.GetType().GetTypeInfo().GetCustomAttribute().Id; + } + + // Add discriminator field + writer.WriteStartObject(); + writer.WritePropertyName(Discriminator); + writer.WriteValue(typeName); + JsonConverterHelper.SerializeProperties(writer, value, serializer); + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/ReadOnlyJsonContractResolver.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/ReadOnlyJsonContractResolver.cs new file mode 100644 index 0000000000000..0569e4e9ec4f5 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/ReadOnlyJsonContractResolver.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Rest.Serialization +{ + /// + /// JSON contract resolver that ignores read-only properties during serialization. + /// + public class ReadOnlyJsonContractResolver : DefaultContractResolver + { + /// + /// Creates a JsonProperty for the given MemberInfo. + /// + /// The member to create a JsonProperty for. + /// The member's parent MemberSerialization. + /// A created JsonProperty for the given MemberInfo. + protected override JsonProperty CreateProperty( + MemberInfo member, + MemberSerialization memberSerialization) + { + JsonProperty jsonProperty = base.CreateProperty(member, memberSerialization); + var propertyInfo = member as PropertyInfo; + + if (propertyInfo != null) + { + jsonProperty.ShouldSerialize = t => + (propertyInfo.SetMethod != null && !propertyInfo.SetMethod.IsPrivate) || + (propertyInfo.GetMethod != null && propertyInfo.GetMethod.IsStatic); + } + + return jsonProperty; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/SafeJsonConvert.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/SafeJsonConvert.cs new file mode 100644 index 0000000000000..778d3120b0680 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/SafeJsonConvert.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.IO; +using Newtonsoft.Json; + +namespace Microsoft.Rest.Serialization +{ + /// + /// Provides an alternative to JSON.NET's JsonConvert that does not inherit any settings from + /// JsonConvert.DefaultSettings. + /// + public static class SafeJsonConvert + { + /// + /// Deserializes the given JSON into an instance of type T. + /// + /// The type to which to deserialize. + /// The JSON to deserialize. + /// JsonSerializerSettings to control deserialization. + /// An instance of type T deserialized from the given JSON. + public static T DeserializeObject(string json, JsonSerializerSettings settings) + { + if (json == null) + { + throw new ArgumentNullException("json"); + } + + // Use Create() instead of CreateDefault() here so that our own settings aren't merged with the defaults. + var serializer = JsonSerializer.Create(settings); + serializer.CheckAdditionalContent = true; + + using (var reader = new JsonTextReader(new StringReader(json))) + { + return (T)serializer.Deserialize(reader, typeof(T)); + } + } + + /// + /// Deserializes the given JSON into an instance of type T using the given JsonConverters. + /// + /// The type to which to deserialize. + /// The JSON to deserialize. + /// A collection of JsonConverters to control deserialization. + /// An instance of type T deserialized from the given JSON. + public static T DeserializeObject(string json, params JsonConverter[] converters) + { + return DeserializeObject(json, SettingsFromConverters(converters)); + } + + /// + /// Serializes the given object into JSON. + /// + /// The object to serialize. + /// JsonSerializerSettings to control serialization. + /// A string containing the JSON representation of the given object. + public static string SerializeObject(object obj, JsonSerializerSettings settings) + { + // Use Create() instead of CreateDefault() here so that our own settings aren't merged with the defaults. + var serializer = JsonSerializer.Create(settings); + var stringWriter = new StringWriter(CultureInfo.InvariantCulture); + + using (var jsonWriter = new JsonTextWriter(stringWriter) { Formatting = serializer.Formatting }) + { + serializer.Serialize(jsonWriter, obj); + } + + return stringWriter.ToString(); + } + + /// + /// Serializes the given object into JSON using the given JsonConverters. + /// + /// The object to serialize. + /// A collection of JsonConverters to control serialization. + /// A string containing the JSON representation of the given object. + public static string SerializeObject(object obj, params JsonConverter[] converters) + { + return SerializeObject(obj, SettingsFromConverters(converters)); + } + + private static JsonSerializerSettings SettingsFromConverters(JsonConverter[] converters) + { + return (converters != null && converters.Length > 0) ? + new JsonSerializerSettings() { Converters = converters } : null; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/TransformationJsonConverter.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/TransformationJsonConverter.cs new file mode 100644 index 0000000000000..9f6b948fcbc56 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/TransformationJsonConverter.cs @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Rest.Serialization +{ + /// + /// JsonConverter that provides serialization for with optional object transformation. + /// + public class TransformationJsonConverter : JsonConverter + { + /// + /// Returns true if the object being serialized contains JsonTransformationAttribute. False otherwise. + /// + /// The type of the object to check. + /// True if the object being serialized contains JsonTransformationAttribute. False otherwise. + public override bool CanConvert(Type objectType) + { + return objectType.GetTypeInfo().GetCustomAttribute(typeof(JsonTransformationAttribute)) != null; + } + + /// + /// Deserializes an object from a JSON string and flattens out Properties. + /// + /// The JSON reader. + /// The type of the object. + /// The existing value. + /// The JSON serializer. + /// + public override object ReadJson(JsonReader reader, + Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader == null) + { + throw new ArgumentNullException("reader"); + } + if (objectType == null) + { + throw new ArgumentNullException("objectType"); + } + if (serializer == null) + { + throw new ArgumentNullException("serializer"); + } + + try + { + JObject jsonObject = JObject.Load(reader); + + // Update type if there is a polymorphism + var polymorphicDeserializer = serializer.Converters + .FirstOrDefault(c => + c.GetType().GetTypeInfo().IsGenericType && + c.GetType().GetGenericTypeDefinition() == typeof(PolymorphicDeserializeJsonConverter<>) && + c.CanConvert(objectType)) as PolymorphicJsonConverter; + if (polymorphicDeserializer != null) + { + objectType = PolymorphicJsonConverter.GetDerivedType(objectType, + (string) jsonObject[polymorphicDeserializer.Discriminator]) ?? objectType; + } + + // Initialize appropriate type instance + var resource = Activator.CreateInstance(objectType); + + // For each property in resource - populate property + var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType); + foreach (JsonProperty property in contract.Properties) + { + JToken propertyValueToken; + string[] parentPath; + string propertyName = property.GetPropertyName(out parentPath); + + if (parentPath.Length > 0) + { + string jsonPath = string.Concat(parentPath.Select(p => $"['{p}']")); + propertyValueToken = jsonObject.SelectToken(jsonPath, false); + if (propertyValueToken != null) + { + propertyValueToken = propertyValueToken[propertyName]; + } + } + else + { + propertyValueToken = jsonObject[propertyName]; + } + + if (propertyValueToken != null && property.Writable) + { + var propertyValue = propertyValueToken.ToObject(property.PropertyType, serializer); + property.ValueProvider.SetValue(resource, propertyValue); + } + } + return resource; + } + catch (JsonException) + { + return null; + } + } + + /// + /// Serializes an object into a JSON string adding Properties. + /// + /// The JSON writer. + /// The value to serialize. + /// The JSON serializer. + public override void WriteJson(JsonWriter writer, + object value, JsonSerializer serializer) + { + if (writer == null) + { + throw new ArgumentNullException("writer"); + } + if (value == null) + { + throw new ArgumentNullException("value"); + } + if (serializer == null) + { + throw new ArgumentNullException("serializer"); + } + + JObject jsonObject = new JObject(); + + // Add discriminator field + var polymorphicSerializer = serializer.Converters + .FirstOrDefault(c => + c.GetType().GetTypeInfo().IsGenericType && + c.GetType().GetGenericTypeDefinition() == typeof(PolymorphicSerializeJsonConverter<>) && + c.CanConvert(value.GetType())) as PolymorphicJsonConverter; + + if (polymorphicSerializer != null) + { + string typeName = value.GetType().Name; + if (value.GetType().GetTypeInfo().GetCustomAttributes().Any()) + { + typeName = value.GetType().GetTypeInfo().GetCustomAttribute().Id; + } + jsonObject.Add(polymorphicSerializer.Discriminator, typeName); + } + + JsonObjectContract contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType()); + foreach (JsonProperty property in contract.Properties) + { + object memberValue = property.ValueProvider.GetValue(value); + + // Skipping properties with null value if NullValueHandling is set to Ignore + if (serializer.NullValueHandling == NullValueHandling.Ignore && + memberValue == null) + { + continue; + } + + // Skipping properties with JsonIgnore attribute, non-readable, and + // ShouldSerialize returning false when set + if (!property.Ignored && property.Readable && + (property.ShouldSerialize == null || property.ShouldSerialize(memberValue))) + { + string[] parentPath; + string propertyName = property.GetPropertyName(out parentPath); + + // Build hierarchy if necessary + JObject parentObject = jsonObject; + for (int i = 0; i < parentPath.Length; i++) + { + JObject childToken = parentObject[parentPath[i]] as JObject; + if (childToken == null) + { + parentObject[parentPath[i]] = new JObject(); + } + parentObject = parentObject[parentPath[i]] as JObject; + } + + parentObject[propertyName] = JToken.FromObject(memberValue, serializer); + } + } + + jsonObject.WriteTo(writer, serializer.Converters.ToArray()); + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/UnixTimeJsonConverter.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/UnixTimeJsonConverter.cs new file mode 100644 index 0000000000000..fd345541a6de0 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/Serialization/UnixTimeJsonConverter.cs @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.Rest.Serialization +{ + public class UnixTimeJsonConverter : JsonConverter + { + public static readonly DateTime EpochDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + /// + /// Converts a byte array to a Base64Url encoded string + /// + /// The byte array to convert + /// The Base64Url encoded form of the input + private static long? ToUnixTime(DateTime dateTime) + { + return (long?)dateTime.Subtract(EpochDate).TotalSeconds; + } + + /// + /// Converts a Base64Url encoded string to a byte array + /// + /// The Base64Url encoded string + /// The byte array represented by the enconded string + private static DateTime? FromUnixTime(long? seconds) + { + if (seconds.HasValue) + { + return EpochDate.AddSeconds(seconds.Value); + } + return null; + } + + public override bool CanConvert(Type objectType) + { + if (objectType == typeof(DateTime?) || objectType == typeof(DateTime)) + return true; + + return false; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (objectType != typeof(DateTime?)) + { + return serializer.Deserialize(reader, objectType); + } + else + { + var value = serializer.Deserialize(reader); + + if (value.HasValue) + { + return FromUnixTime(value); + } + } + + return null; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value.GetType() != typeof(DateTime)) + { + JToken.FromObject(value).WriteTo(writer); + } + else + { + JToken.FromObject(ToUnixTime((DateTime)value)).WriteTo(writer); + } + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/SerializationException.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/SerializationException.cs new file mode 100644 index 0000000000000..e449abd5aef18 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/SerializationException.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Runtime.Serialization; +#if !PORTABLE +using System.Security.Permissions; +#endif + +namespace Microsoft.Rest +{ + /// + /// Serialization exception for Microsoft Rest Client. + /// +#if !PORTABLE + [Serializable] +#endif + public class SerializationException : RestException + { + + /// + /// Initializes a new instance of the SerializationException class. + /// + public SerializationException() + : base() + { + } + + /// + /// Initializes a new instance of the SerializationException class. + /// + /// Exception message. + public SerializationException(string message) + : this(message, null) + { + } + + /// + /// Initializes a new instance of the SerializationException class. + /// + /// Exception message. + /// Inner exception. + public SerializationException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the SerializationException class from a message and a content. + /// + /// The exception message + /// The failed content + /// The inner AdalException with additional details + public SerializationException(string message, string content, Exception innerException) : + base(message, innerException) + { + Content = content; + } + + /// + /// Gets of sets content that failed to serialize. + /// + public string Content { get; set; } + +#if !PORTABLE + /// + /// Initializes a new instance of the SerializationException class. + /// + /// Serialization info. + /// Streaming context. + protected SerializationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Serializes content of the exception. + /// + /// Serialization info. + /// Streaming context. + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + if (info == null) + { + throw new ArgumentNullException("info"); + } + + info.AddValue("Content", Content); + } +#endif + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClient.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClient.cs new file mode 100644 index 0000000000000..7aa3a8c3d7ca9 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClient.cs @@ -0,0 +1,416 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using Microsoft.Rest.TransientFaultHandling; +#if net45 +using Microsoft.Win32; +#endif +namespace Microsoft.Rest +{ + /// + /// ServiceClient is the abstraction for accessing REST operations and their payload data types.. + /// + /// Type of the ServiceClient. + public abstract class ServiceClient : IDisposable + where T : ServiceClient + { + /// + /// ProductName string to be used to set Framework Version in UserAgent + /// + private const string FXVERSION = "FxVersion"; + private const string OSNAME = "OSName"; + private const string OSVERSION = "OSVersion"; + + /// + /// Indicates whether the ServiceClient has been disposed. + /// + private bool _disposed; + + /// + /// Field used for ClientVersion property + /// + private string _clientVersion; + + /// + /// Field used for Framework Version property + /// + private string _fxVersion; + +#if net45 + /// + /// Indicates OS Name + /// + private string _osName; + + /// + /// Indicates OS Version + /// + private string _osVersion; + + /// + /// Gets Os Information, OSName - OS Major.Minor.Build version + /// e.g. Windows 10 Enterprise - 6.3.14393 + /// + private string OsName + { + get + { + if(string.IsNullOrEmpty(_osName)) + { + _osName = ReadHKLMRegistry(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName"); + } + + // If you want to log OsName in userAgent, it has to be without spaces + if (!string.IsNullOrEmpty(_osName)) + { + _osName = _osName.Replace(" ", "_"); + } + + return _osName; + } + } + + /// + /// Gets Os Major.Minor.Build version + /// e.g. 6.3.14393 + /// + private string OsVersion + { + get + { + if (string.IsNullOrEmpty(_osVersion)) + { + string osMajorMinorVersion = ReadHKLMRegistry(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentVersion"); + string osBuildNumber = ReadHKLMRegistry(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentBuild"); + _osVersion = string.Format("{0}.{1}", osMajorMinorVersion, osBuildNumber); + } + + return _osVersion; + } + } + + /// + /// Reads HKLM registry key from the provided path/key combination + /// + /// Path to HKLM key + /// HKLM key name + /// Value for provided HKLM key + private string ReadHKLMRegistry(string path, string key) + { + try + { + using (RegistryKey rk = Registry.LocalMachine.OpenSubKey(path)) + { + if (rk == null) return ""; + return (string)rk.GetValue(key); + } + } + catch { return ""; } + } +#endif + + /// + /// Gets the AssemblyInformationalVersion if available + /// if not it gets the AssemblyFileVerion + /// if neither are available it will default to the Assembly Version of a service client. + /// + private string ClientVersion + { + get + { + if (string.IsNullOrEmpty(_clientVersion)) + { + Type type = this.GetType(); + Assembly assembly = type.GetTypeInfo().Assembly; + + try + { + // try to get AssemblyInformationalVersion first + AssemblyInformationalVersionAttribute aivAttribute = + assembly.GetCustomAttribute(typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute; + _clientVersion = aivAttribute?.InformationalVersion; + + // if not available try to get AssemblyFileVersion + if (String.IsNullOrEmpty(_clientVersion)) + { + AssemblyFileVersionAttribute fvAttribute = + assembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute)) as AssemblyFileVersionAttribute; + _clientVersion = fvAttribute?.Version; + } + } + catch (AmbiguousMatchException) + { + // in case there are more then one attribute of the type + } + + // no usable version attribute found so default to Assembly Version + if (String.IsNullOrEmpty(_clientVersion)) + { + _clientVersion = + assembly + .FullName + .Split(',') + .Select(c => c.Trim()) + .First(c => c.StartsWith("Version=", StringComparison.OrdinalIgnoreCase)) + .Substring("Version=".Length); + } + + } + return _clientVersion; + } + } + + /// + /// Get file version for System.dll + /// + private string FrameworkVersion + { + get + { + if (string.IsNullOrEmpty(_fxVersion)) + { + Assembly assembly = typeof(Object).GetTypeInfo().Assembly; + AssemblyFileVersionAttribute fvAttribute = + assembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute)) as AssemblyFileVersionAttribute; + _fxVersion = fvAttribute?.Version; + } + + return _fxVersion; + } + } + + /// + /// Reference to the first HTTP handler (which is the start of send HTTP + /// pipeline). + /// + protected HttpMessageHandler FirstMessageHandler { get; set; } + + /// + /// Reference to the innermost HTTP handler (which is the end of send HTTP + /// pipeline). + /// + protected HttpClientHandler HttpClientHandler { get; set; } + + /// + /// Initializes a new instance of the ServiceClient class. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "The created objects should be disposed on caller's side")] + protected ServiceClient() + : this(CreateRootHandler()) + { + } + + /// + /// Initializes a new instance of the ServiceClient class. + /// + /// List of handlers from top to bottom (outer handler is the first in the list) + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "The created objects should be disposed on caller's side")] + protected ServiceClient(params DelegatingHandler[] handlers) + : this(CreateRootHandler(), handlers) + { + } + + /// + /// Initializes ServiceClient using base HttpClientHandler and list of handlers. + /// + /// Base HttpClientHandler. + /// List of handlers from top to bottom (outer handler is the first in the list) + protected ServiceClient(HttpClientHandler rootHandler, params DelegatingHandler[] handlers) + { + InitializeHttpClient(rootHandler, handlers); + } + + /// + /// Create a new instance of the root handler. + /// + /// HttpClientHandler created. + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "The created objects should be disposed on caller's side")] + protected static HttpClientHandler CreateRootHandler() + { + // Create our root handler +#if NET45 + return new WebRequestHandler(); +#else + return new HttpClientHandler(); +#endif + } + + /// + /// Gets the HttpClient used for making HTTP requests. + /// + public HttpClient HttpClient { get; protected set; } + + /// + /// Gets the UserAgent collection which can be augmented with custom + /// user agent strings. + /// + public virtual HttpHeaderValueCollection UserAgent + { + get { return HttpClient.DefaultRequestHeaders.UserAgent; } + } + + /// + /// Get the HTTP pipelines for the given service client. + /// + /// The client's HTTP pipeline. + public virtual IEnumerable HttpMessageHandlers + { + get + { + var handler = FirstMessageHandler; + + while (handler != null) + { + yield return handler; + + DelegatingHandler delegating = handler as DelegatingHandler; + handler = delegating != null ? delegating.InnerHandler : null; + } + } + } + + /// + /// Sets retry policy for the client. + /// + /// Retry policy to set. + public virtual void SetRetryPolicy(RetryPolicy retryPolicy) + { + if (retryPolicy == null) + { + retryPolicy = new RetryPolicy(0); + } + + RetryDelegatingHandler delegatingHandler = + HttpMessageHandlers.OfType().FirstOrDefault(); + if (delegatingHandler != null) + { + delegatingHandler.RetryPolicy = retryPolicy; + } + else + { + throw new InvalidOperationException(ClientRuntime.Properties.Resources.ExceptionRetryHandlerMissing); + } + } + + /// + /// Dispose the ServiceClient. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose the HttpClient and Handlers. + /// + /// True to release both managed and unmanaged resources; false to releases only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + _disposed = true; + + // Dispose the client + HttpClient.Dispose(); + HttpClient = null; + FirstMessageHandler = null; + HttpClientHandler = null; + } + } + + /// + /// Initializes HttpClient using HttpClientHandler. + /// + /// Base HttpClientHandler. + /// List of handlers from top to bottom (outer handler is the first in the list) + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Reliability", + "CA2000:Dispose objects before losing scope", + Justification = "We let HttpClient instance dispose")] + protected void InitializeHttpClient(HttpClientHandler httpClientHandler, params DelegatingHandler[] handlers) + { + HttpClientHandler = httpClientHandler; + DelegatingHandler currentHandler = new RetryDelegatingHandler(); + currentHandler.InnerHandler = HttpClientHandler; + + if (handlers != null) + { + for (int i = handlers.Length - 1; i >= 0; --i) + { + DelegatingHandler handler = handlers[i]; + // Non-delegating handlers are ignored since we always + // have RetryDelegatingHandler as the outer-most handler + while (handler.InnerHandler is DelegatingHandler) + { + handler = handler.InnerHandler as DelegatingHandler; + } + handler.InnerHandler = currentHandler; + currentHandler = handlers[i]; + } + } + + var newClient = new HttpClient(currentHandler, false); + FirstMessageHandler = currentHandler; + HttpClient = newClient; + Type type = this.GetType(); + SetUserAgent(type.FullName, ClientVersion); + } + + /// + /// Sets the product name to be used in the user agent header when making requests + /// + /// Name of the product to be used in the user agent + public bool SetUserAgent(string productName) + { + return SetUserAgent(productName, ClientVersion); + } + + /// + /// Sets the product name and version to be used in the user agent header when making requests + /// + /// Name of the product to be used in the user agent + /// Version of the product to be used in the user agent + public bool SetUserAgent(string productName, string version) + { + if (!_disposed && HttpClient != null) + { + SetDefaultUserAgentInfo(); + HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(productName, version)); + return true; + } + + // Returns false if the HttpClient was disposed before invoking the method + return false; + } + + /// + /// Set Default information in User Agent + /// + /// + private void SetDefaultUserAgentInfo() + { + HttpClient.DefaultRequestHeaders.UserAgent.Clear(); + HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(FXVERSION, FrameworkVersion)); +#if net45 + // If you want to log ProductName in userAgent, it has to be without spaces + HttpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(OsName, OsVersion)); +#endif + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClientCredentials.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClientCredentials.cs new file mode 100644 index 0000000000000..c9bbbbbc55c5a --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClientCredentials.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Rest +{ + /// + /// ServiceClientCredentials is the abstraction for credentials used by ServiceClients accessing REST services. + /// + public abstract class ServiceClientCredentials + { + /// + /// Initialize a ServiceClient instance for accessing REST APIs with these credentials. + /// + /// Type of ServiceClient. + /// The ServiceClient. + public virtual void InitializeServiceClient(ServiceClient client) + where T : ServiceClient + { + } + + /// + /// Apply the credentials to the HTTP request. + /// + /// The HTTP request message. + /// Cancellation token. + /// + /// Task that will complete when processing has finished. + /// + public virtual Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + // Return an empty task by default + return Task.FromResult(null); + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClientTracing.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClientTracing.cs new file mode 100644 index 0000000000000..1343b5edcf0b0 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ServiceClientTracing.cs @@ -0,0 +1,247 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net.Http; +using System.Threading; + +namespace Microsoft.Rest +{ + /// + /// Provides a set of methods and properties that help you trace the service client. + /// + public static class ServiceClientTracing + { + /// + /// The collection of tracing interceptors to notify. + /// + private static readonly List _interceptors = + new List(); + + /// + /// A read-only, thread-safe collection of tracing interceptors. Since + /// List is only thread-safe for reads (and adding/removing tracing + /// interceptors isn't a very common operation), we simply replace the + /// entire collection of interceptors so any enumeration of the list + /// in progress on a different thread will not be affected by the + /// change. + /// + private static List _threadSafeInterceptors = + new List(); + + /// + /// Lock used to synchronize mutation of the tracing interceptors. + /// + private static readonly object _lock = new object(); + + /// + /// The invocation identifier. + /// + private static long _nextInvocationId = 0; + + private static bool _isEnabled = false; + + /// + /// Gets or sets a value indicating whether tracing is enabled. + /// Tracing can be disabled for performance. + /// + public static bool IsEnabled + { + get { return _isEnabled; } + set { _isEnabled = value; } + } + + /// + /// Gets a sequence of the tracing interceptors to notify of changes. + /// + internal static IEnumerable TracingInterceptors + { + get { return _threadSafeInterceptors; } + } + + /// + /// Get the next invocation identifier. + /// + public static long NextInvocationId + { + get + { + // In the event of long.MaxValue requests, this will + // automatically rollover + return Interlocked.Increment(ref _nextInvocationId); + } + } + + /// + /// Add a tracing interceptor to be notified of changes. + /// + /// The tracing interceptor. + public static void AddTracingInterceptor(IServiceClientTracingInterceptor interceptor) + { + if (interceptor == null) + { + throw new ArgumentNullException("interceptor"); + } + + lock (_lock) + { + _interceptors.Add(interceptor); + _threadSafeInterceptors = new List(_interceptors); + } + } + + /// + /// Remove a tracing interceptor from change notifications. + /// + /// The tracing interceptor. + /// True if the tracing interceptor was found and removed; false otherwise. + public static bool RemoveTracingInterceptor(IServiceClientTracingInterceptor interceptor) + { + if (interceptor == null) + { + throw new ArgumentNullException("interceptor"); + } + + bool removed; + lock (_lock) + { + removed = _interceptors.Remove(interceptor); + if (removed) + { + _threadSafeInterceptors = new List(_interceptors); + } + } + return removed; + } + + /// + /// Write the informational tracing message. + /// + /// The message to trace. + /// An object array containing zero or more objects to format + public static void Information(string message, params object[] parameters) + { + if (IsEnabled) + { + Information(string.Format(CultureInfo.InvariantCulture, message, parameters)); + } + } + + /// + /// Represents the tracing configuration for the value of a setting. + /// + /// The configuration source. + /// The name of the setting. + /// The value of the setting in the source. + public static void Configuration(string source, string name, string value) + { + if (IsEnabled) + { + foreach (IServiceClientTracingInterceptor writer in TracingInterceptors) + { + writer.Configuration(source, name, value); + } + } + } + + /// + /// Specifies the tracing information. + /// + /// The message to trace. + public static void Information(string message) + { + if (IsEnabled) + { + foreach (IServiceClientTracingInterceptor writer in TracingInterceptors) + { + writer.Information(message); + } + } + } + + /// + /// Represents the tracing entry. + /// + /// + /// The tracing instance. + /// The tracing method. + /// Method parameters. + public static void Enter(string invocationId, object instance, string method, + IDictionary parameters) + { + if (IsEnabled) + { + foreach (IServiceClientTracingInterceptor writer in TracingInterceptors) + { + writer.EnterMethod(invocationId, instance, method, parameters); + } + } + } + + /// + /// Sends a tracing request. + /// + /// The invocation identifier. + /// The request about to be sent. + public static void SendRequest(string invocationId, HttpRequestMessage request) + { + if (IsEnabled) + { + foreach (IServiceClientTracingInterceptor writer in TracingInterceptors) + { + writer.SendRequest(invocationId, request); + } + } + } + + /// + /// Receives a tracing response. + /// + /// The invocation identifier. + /// The response message instance. + public static void ReceiveResponse(string invocationId, HttpResponseMessage response) + { + if (IsEnabled) + { + foreach (IServiceClientTracingInterceptor writer in TracingInterceptors) + { + writer.ReceiveResponse(invocationId, response); + } + } + } + + /// + /// Represents the tracing error. + /// + /// The invocation identifier. + /// The tracing exception. + public static void Error(string invocationId, Exception ex) + { + if (IsEnabled) + { + foreach (IServiceClientTracingInterceptor writer in TracingInterceptors) + { + writer.TraceError(invocationId, ex); + } + } + } + + /// + /// Abandons the tracing method. + /// + /// The invocation identifier. + /// Method return result. + public static void Exit(string invocationId, object result) + { + if (IsEnabled) + { + foreach (IServiceClientTracingInterceptor writer in TracingInterceptors) + { + writer.ExitMethod(invocationId, result); + } + } + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/StringTokenProvider.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/StringTokenProvider.cs new file mode 100644 index 0000000000000..d732ea99b8839 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/StringTokenProvider.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Rest +{ + /// + /// A simple token provider that always provides a static access token. + /// + public sealed class StringTokenProvider : ITokenProvider + { + private string _accessToken; + private string _type; + + /// + /// Create a token provider for the given token type that returns the given + /// access token. + /// + /// The access token to return. + /// The token type of the given access token. + public StringTokenProvider(string accessToken, string tokenType) + { + _accessToken = accessToken; + _type = tokenType; + } + + /// + /// Gets the token type of this access token. + /// + public string TokenType + { + get { return _type; } + } + + /// + /// Returns the static access token. + /// + /// The cancellation token for this action. + /// This will not be used since the returned token is static. + /// The access token. + public Task GetAuthenticationHeaderAsync(CancellationToken cancellationToken) + { + return Task.FromResult(new AuthenticationHeaderValue(_type, _accessToken)); + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TokenCredentials.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TokenCredentials.cs new file mode 100644 index 0000000000000..264b55b37a73a --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TokenCredentials.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Rest.ClientRuntime.Properties; + +namespace Microsoft.Rest +{ + /// + /// Token based credentials for use with a REST Service Client. + /// + public class TokenCredentials : ServiceClientCredentials + { + /// + /// The bearer token type, as serialized in an http Authentication header. + /// + private const string BearerTokenType = "Bearer"; + + /// + /// Gets or sets secure token used to authenticate against Microsoft Azure API. + /// No anonymous requests are allowed. + /// + protected ITokenProvider TokenProvider { get; private set; } + + /// + /// Gets Tenant ID + /// + public string TenantId { get; private set; } + + /// + /// Gets UserInfo.DisplayableId + /// + public string CallerId { get; private set; } + + /// + /// Initializes a new instance of the + /// class with the given 'Bearer' token. + /// + /// Valid JSON Web Token (JWT). + public TokenCredentials(string token) + : this(token, BearerTokenType) + { + } + + /// + /// Initializes a new instance of the + /// class with the given token and token type. + /// + /// Valid JSON Web Token (JWT). + /// The token type of the given token. + public TokenCredentials(string token, string tokenType) + : this(new StringTokenProvider(token, tokenType)) + { + if (string.IsNullOrEmpty(token)) + { + throw new ArgumentNullException("token"); + } + if (string.IsNullOrEmpty(tokenType)) + { + throw new ArgumentNullException("tokenType"); + } + } + + /// + /// Create an access token credentials object, given an interface to a token source. + /// + /// The source of tokens for these credentials. + public TokenCredentials(ITokenProvider tokenProvider) + { + if (tokenProvider == null) + { + throw new ArgumentNullException("tokenProvider"); + } + + this.TokenProvider = tokenProvider; + } + + /// + /// Create an access token credentials object, given an interface to a token source. + /// + /// The source of tokens for these credentials. + /// Tenant ID from AuthenticationResult + /// UserInfo.DisplayableId field from AuthenticationResult + public TokenCredentials(ITokenProvider tokenProvider, string tenantId, string callerId) + : this(tokenProvider) + { + this.TenantId = tenantId; + this.CallerId = callerId; + } + + /// + /// Apply the credentials to the HTTP request. + /// + /// The HTTP request. + /// Cancellation token. + /// + /// Task that will complete when processing has completed. + /// + public async override Task ProcessHttpRequestAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + if (request == null) + { + throw new ArgumentNullException("request"); + } + + if (TokenProvider == null) + { + throw new InvalidOperationException(Resources.TokenProviderCannotBeNull); + } + + request.Headers.Authorization = await TokenProvider.GetAuthenticationHeaderAsync(cancellationToken); + await base.ProcessHttpRequestAsync(request, cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/AsyncExecution.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/AsyncExecution.cs new file mode 100644 index 0000000000000..f29b9ff435533 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/AsyncExecution.cs @@ -0,0 +1,239 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Rest.ClientRuntime.Properties; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Handles the execution and retries of the user-initiated task. + /// + /// The result type of the user-initiated task. + internal class AsyncExecution + { + private readonly CancellationToken _cancellationToken; + private readonly bool _fastFirstRetry; + private readonly Func _isTransient; + private readonly Action _onRetrying; + private readonly ShouldRetryHandler _shouldRetryHandler; + private readonly Func> _taskFunc; + + private Task _previousTask; + private int _retryCount; + + public AsyncExecution( + Func> taskFunc, + ShouldRetryHandler shouldRetryHandler, + Func isTransient, + Action onRetrying, + bool fastFirstRetry, + CancellationToken cancellationToken) + { + _taskFunc = taskFunc; + _shouldRetryHandler = shouldRetryHandler; + _isTransient = isTransient; + _onRetrying = onRetrying; + _fastFirstRetry = fastFirstRetry; + _cancellationToken = cancellationToken; + } + + internal Task ExecuteAsync() + { + return ExecuteAsyncImpl(null); + } + + private Task ExecuteAsyncImpl(Task ignore) + { + if (_cancellationToken.IsCancellationRequested) + { + // if retry was canceled before retrying after a failure, return the failed task. + if (_previousTask != null) + { + return _previousTask; + } + else + { + var tcs = new TaskCompletionSource(); + tcs.TrySetCanceled(); + return tcs.Task; + } + } + + // This is a little different from ExecuteAction using APM. If an exception occurs synchronously when + // starting the task, then the exception is checked for transient errors and if the exception is not + // transient, it will bubble up synchronously. Otherwise it will be retried. The reason for bubbling up + // synchronously -instead of returning a failed task- is that TAP design guidelines dictate that task + // creation should only fail synchronously in response to a usage error, which can be avoided by changing + // the code that calls the method, and hence should not be considered transient. Nevertheless, as this is + // a general purpose transient error detection library, we cannot guarantee that other libraries or user + // code will follow the design guidelines. + Task task; + try + { + task = _taskFunc.Invoke(); + } + catch (Exception ex) + { + if (_isTransient(ex)) + { + var tcs = new TaskCompletionSource(); + tcs.TrySetException(ex); + task = tcs.Task; + } + else + { + throw; + } + } + + // Fast path if the user-initiated task is already completed. + if (task.Status == TaskStatus.RanToCompletion) + { + return task; + } + + if (task.Status == TaskStatus.Created) + { + throw new InvalidOperationException( + string.Format(CultureInfo.InvariantCulture, Resources.TaskMustBeScheduled, "taskFunc")); + } + + return task + .ContinueWith>(ExecuteAsyncContinueWith, CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default) + .Unwrap(); + } + + private Task ExecuteAsyncContinueWith(Task runningTask) + { + if (!runningTask.IsFaulted || _cancellationToken.IsCancellationRequested) + { + return runningTask; + } + + Exception lastError = runningTask.Exception.InnerException; + + if (!(_isTransient(lastError))) + { + // if not transient, return the faulted running task. + return runningTask; + } + + RetryCondition condition = _shouldRetryHandler(_retryCount++, lastError); + if (!condition.RetryAllowed) + { + return runningTask; + } + TimeSpan delay = condition.DelayBeforeRetry; + + // Perform an extra check in the delay interval. + if (delay < TimeSpan.Zero) + { + delay = TimeSpan.Zero; + } + + _onRetrying(_retryCount, lastError, delay); + + _previousTask = runningTask; + if (delay > TimeSpan.Zero && (_retryCount > 1 || !_fastFirstRetry)) + { + return Task.Delay(delay) + .ContinueWith>(ExecuteAsyncImpl, CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default) + .Unwrap(); + } + + return ExecuteAsyncImpl(null); + } + } + + /// + /// Provides a wrapper for a non-generic and calls into the pipeline + /// to retry only the generic version of the . + /// + internal class AsyncExecution : AsyncExecution + { + private static Task _cachedBoolTask; + + public AsyncExecution( + Func taskAction, + ShouldRetryHandler shouldRetryHandler, + Func isTransient, + Action onRetrying, + bool fastFirstRetry, + CancellationToken cancellationToken) + : base( + () => StartAsGenericTask(taskAction), + shouldRetryHandler, + isTransient, + onRetrying, + fastFirstRetry, + cancellationToken) + { + } + + private static Task GetCachedTask() + { + if (_cachedBoolTask == null) + { + var tcs = new TaskCompletionSource(); + tcs.TrySetResult(true); + _cachedBoolTask = tcs.Task; + } + + return _cachedBoolTask; + } + + /// + /// Wraps the non-generic into a generic . + /// + /// The task to wrap. + /// A that wraps the non-generic . + private static Task StartAsGenericTask(Func taskAction) + { + var task = taskAction.Invoke(); + if (task == null) + { + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, Resources.TaskCannotBeNull, "taskAction"), + "taskAction"); + } + + if (task.Status == TaskStatus.RanToCompletion) + { + // Fast path if the user-initiated task is already completed. + return GetCachedTask(); + } + + if (task.Status == TaskStatus.Created) + { + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, Resources.TaskMustBeScheduled, "taskAction"), + "taskAction"); + } + + var tcs = new TaskCompletionSource(); + task.ContinueWith(t => + { + if (t.IsFaulted) + { + tcs.TrySetException(t.Exception.InnerExceptions); + } + else if (t.IsCanceled) + { + tcs.TrySetCanceled(); + } + else + { + tcs.TrySetResult(true); + } + }, TaskContinuationOptions.ExecuteSynchronously); + return tcs.Task; + } + + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/ExponentialBackoffRetryStrategy.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/ExponentialBackoffRetryStrategy.cs new file mode 100644 index 0000000000000..b1241b6dcbe6c --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/ExponentialBackoffRetryStrategy.cs @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// A retry strategy with backoff parameters for calculating the exponential delay between retries. + /// + public class ExponentialBackoffRetryStrategy : RetryStrategy + { + /// + /// Represents the default amount of time used when calculating a random delta in the exponential + /// delay between retries. + /// + public static readonly TimeSpan DefaultClientBackoff = TimeSpan.FromSeconds(10.0); + + /// + /// Represents the default maximum amount of time used when calculating the exponential + /// delay between retries. + /// + public static readonly TimeSpan DefaultMaxBackoff = TimeSpan.FromSeconds(30.0); + + /// + /// Represents the default minimum amount of time used when calculating the exponential + /// delay between retries. + /// + public static readonly TimeSpan DefaultMinBackoff = TimeSpan.FromSeconds(1.0); + + private readonly TimeSpan _deltaBackoff; + private readonly TimeSpan _maxBackoff; + private readonly TimeSpan _minBackoff; + private readonly int _retryCount; + + /// + /// Initializes a new instance of the class. + /// + public ExponentialBackoffRetryStrategy() + : this(DefaultClientRetryCount, DefaultMinBackoff, DefaultMaxBackoff, DefaultClientBackoff) + { + } + + /// + /// Initializes a new instance of the class with the specified + /// retry settings. + /// + /// The maximum number of retry attempts. + /// The minimum backoff time + /// The maximum backoff time. + /// The value that will be used to calculate a random delta in the exponential delay + /// between retries. + public ExponentialBackoffRetryStrategy(int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, + TimeSpan deltaBackoff) + : this(null, retryCount, minBackoff, maxBackoff, deltaBackoff, DefaultFirstFastRetry) + { + } + + /// + /// Initializes a new instance of the class with the specified name and + /// retry settings. + /// + /// The name of the retry strategy. + /// The maximum number of retry attempts. + /// The minimum backoff time + /// The maximum backoff time. + /// The value that will be used to calculate a random delta in the exponential delay + /// between retries. + public ExponentialBackoffRetryStrategy(string name, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, + TimeSpan deltaBackoff) + : this(name, retryCount, minBackoff, maxBackoff, deltaBackoff, DefaultFirstFastRetry) + { + } + + /// + /// Initializes a new instance of the class with the specified name, + /// retry settings, and fast retry option. + /// + /// The name of the retry strategy. + /// The maximum number of retry attempts. + /// The minimum backoff time + /// The maximum backoff time. + /// The value that will be used to calculate a random delta in the exponential delay + /// between retries. + /// true to immediately retry in the first attempt; otherwise, false. The subsequent + /// retries will remain subject to the configured retry interval. + public ExponentialBackoffRetryStrategy(string name, int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, + TimeSpan deltaBackoff, bool firstFastRetry) + : base(name, firstFastRetry) + { + Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); + Guard.ArgumentNotNegativeValue(minBackoff.Ticks, "minBackoff"); + Guard.ArgumentNotNegativeValue(maxBackoff.Ticks, "maxBackoff"); + Guard.ArgumentNotNegativeValue(deltaBackoff.Ticks, "deltaBackoff"); + Guard.ArgumentNotGreaterThan(minBackoff.TotalMilliseconds, maxBackoff.TotalMilliseconds, "minBackoff"); + + _retryCount = retryCount; + _minBackoff = minBackoff; + _maxBackoff = maxBackoff; + _deltaBackoff = deltaBackoff; + } + + /// + /// Returns the corresponding ShouldRetry delegate. + /// + /// The ShouldRetry delegate. + public override ShouldRetryHandler GetShouldRetryHandler() + { + return delegate(int currentRetryCount, Exception lastException) + { + if (currentRetryCount < _retryCount) + { + var random = new Random(); + + var delta = (Math.Pow(2.0, currentRetryCount) - 1.0) * + random.Next((int) (_deltaBackoff.TotalMilliseconds*0.8), + (int) (_deltaBackoff.TotalMilliseconds*1.2)); + var interval = (int) Math.Min(_minBackoff.TotalMilliseconds + delta, + _maxBackoff.TotalMilliseconds); + TimeSpan retryInterval = TimeSpan.FromMilliseconds(interval); + + return new RetryCondition(true, retryInterval); + } + + return new RetryCondition(false, TimeSpan.Zero); + }; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/FixedIntervalRetryStrategy.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/FixedIntervalRetryStrategy.cs new file mode 100644 index 0000000000000..428e7c1bb399a --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/FixedIntervalRetryStrategy.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Represents a retry strategy with a specified number of retry attempts and a default, fixed + /// time interval between retries. + /// + public class FixedIntervalRetryStrategy : RetryStrategy + { + private readonly int _retryCount; + private readonly TimeSpan _retryInterval; + + /// + /// Initializes a new instance of the class. + /// + public FixedIntervalRetryStrategy() + : this(DefaultClientRetryCount) + { + } + + /// + /// Initializes a new instance of the class with the + /// specified number of retry attempts. + /// + /// The number of retry attempts. + public FixedIntervalRetryStrategy(int retryCount) + : this(retryCount, DefaultRetryInterval) + { + } + + /// + /// Initializes a new instance of the class with the + /// specified number of retry attempts and time interval. + /// + /// The number of retry attempts. + /// The time interval between retries. + public FixedIntervalRetryStrategy(int retryCount, TimeSpan retryInterval) + : this(null, retryCount, retryInterval, DefaultFirstFastRetry) + { + } + + /// + /// Initializes a new instance of the class with the + /// specified number of retry attempts, time interval, and retry strategy. + /// + /// The retry strategy name. + /// The number of retry attempts. + /// The time interval between retries. + public FixedIntervalRetryStrategy(string name, int retryCount, TimeSpan retryInterval) + : this(name, retryCount, retryInterval, DefaultFirstFastRetry) + { + } + + /// + /// Initializes a new instance of the class with the + /// specified number of retry attempts, time interval, retry strategy, and fast start option. + /// + /// The retry strategy name. + /// The number of retry attempts. + /// The time interval between retries. + /// true to immediately retry in the first attempt; otherwise, false. + /// The subsequent retries will remain subject to the configured retry interval. + public FixedIntervalRetryStrategy(string name, int retryCount, TimeSpan retryInterval, bool firstFastRetry) + : base(name, firstFastRetry) + { + Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); + Guard.ArgumentNotNegativeValue(retryInterval.Ticks, "retryInterval"); + + _retryCount = retryCount; + _retryInterval = retryInterval; + } + + /// + /// Returns the corresponding ShouldRetry delegate. + /// + /// The ShouldRetry delegate. + public override ShouldRetryHandler GetShouldRetryHandler() + { + if (_retryCount == 0) + { + return + delegate(int currentRetryCount, Exception lastException) + { + return new RetryCondition(false, TimeSpan.Zero); + }; + } + + return delegate(int currentRetryCount, Exception lastException) + { + if (currentRetryCount < _retryCount) + { + return new RetryCondition(true, _retryInterval); + } + return new RetryCondition(false, TimeSpan.Zero); + }; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/Guard.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/Guard.cs new file mode 100644 index 0000000000000..8d70f9f3d1254 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/Guard.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using Microsoft.Rest.ClientRuntime.Properties; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Implements the common guard methods. + /// + internal static class Guard + { + /// + /// Checks an argument to ensure that it isn't null. + /// + /// The argument value to check. + /// The name of the argument. + /// The return value should be ignored. It is intended to be used only when validating arguments during instance creation (for example, when calling the base constructor). + public static bool ArgumentNotNull(object argumentValue, string argumentName) + { + if (argumentValue == null) + { + throw new ArgumentNullException(argumentName); + } + + return true; + } + + /// + /// Checks an argument to ensure that its 32-bit signed value isn't negative. + /// + /// The value of the argument. + /// The name of the argument for diagnostic purposes. + public static void ArgumentNotNegativeValue(int argumentValue, string argumentName) + { + if (argumentValue < 0) + { + throw new ArgumentOutOfRangeException(argumentName, string.Format(CultureInfo.CurrentCulture, + Resources.ArgumentCannotBeNegative, argumentName)); + } + } + + /// + /// Checks an argument to ensure that its 64-bit signed value isn't negative. + /// + /// The value of the argument. + /// The name of the argument for diagnostic purposes. + public static void ArgumentNotNegativeValue(long argumentValue, string argumentName) + { + if (argumentValue < 0) + { + throw new ArgumentOutOfRangeException(argumentName, string.Format(CultureInfo.CurrentCulture, + Resources.ArgumentCannotBeNegative, argumentName)); + } + } + + /// + /// Checks an argument to ensure that its value doesn't exceed the specified ceiling baseline. + /// + /// The value of the argument. + /// The ceiling value of the argument. + /// The name of the argument for diagnostic purposes. + public static void ArgumentNotGreaterThan(double argumentValue, double ceilingValue, string argumentName) + { + if (argumentValue > ceilingValue) + { + throw new ArgumentOutOfRangeException(argumentName, string.Format(CultureInfo.CurrentCulture, + Resources.ArgumentCannotBeGreaterThanBaseline, argumentName, ceilingValue)); + } + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/HttpRequestWithStatusException.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/HttpRequestWithStatusException.cs new file mode 100644 index 0000000000000..a1f47d1f5fc48 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/HttpRequestWithStatusException.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Net.Http; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Inherits HttpRequestException adding HttpStatusCode to the exception. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Design", + "CA1032:ImplementStandardExceptionConstructors"), + System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Usage", + "CA2237:MarkISerializableTypesWithSerializable", + Justification = "HttpRequestException hides the constructor needed for serialization.")] + public class HttpRequestWithStatusException : HttpRequestException + { + /// + /// Initializes a new instance of the class. + /// + public HttpRequestWithStatusException() + { + } + + /// + /// Initializes a new instance of the class + /// with a specific message that describes the current exception. + /// + /// A message that describes the current exception. + public HttpRequestWithStatusException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class + /// with a specific message that describes the current exception and an inner + /// exception. + /// + /// A message that describes the current exception. + /// The inner exception. + public HttpRequestWithStatusException(string message, Exception inner) : base(message, inner) + { + } + + /// + /// Http status code. + /// + public HttpStatusCode StatusCode { get; set; } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/HttpStatusCodeErrorDetectionStrategy.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/HttpStatusCodeErrorDetectionStrategy.cs new file mode 100644 index 0000000000000..ec1fa6706c54d --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/HttpStatusCodeErrorDetectionStrategy.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Net; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Default Http error detection strategy based on Http Status Code. + /// + public class HttpStatusCodeErrorDetectionStrategy : ITransientErrorDetectionStrategy + { + /// + /// Returns true if status code in HttpRequestExceptionWithStatus exception is greater + /// than or equal to 500 and not NotImplemented (501) or HttpVersionNotSupported (505). + /// + /// Exception to check against. + /// True if exception is transient otherwise false. + public bool IsTransient(Exception ex) + { + if (ex != null) + { + HttpRequestWithStatusException httpException; + if ((httpException = ex as HttpRequestWithStatusException) != null) + { + if (httpException.StatusCode == HttpStatusCode.RequestTimeout || + (httpException.StatusCode >= HttpStatusCode.InternalServerError && + httpException.StatusCode != HttpStatusCode.NotImplemented && + httpException.StatusCode != HttpStatusCode.HttpVersionNotSupported)) + { + return true; + } + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/ITransientErrorDetectionStrategy.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/ITransientErrorDetectionStrategy.cs new file mode 100644 index 0000000000000..0d2f71ce6132e --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/ITransientErrorDetectionStrategy.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Defines an interface that must be implemented by custom components responsible for + /// detecting specific transient conditions. + /// + public interface ITransientErrorDetectionStrategy + { + /// + /// Determines whether the specified exception represents a transient failure that + /// can be compensated by a retry. + /// + /// The exception object to be verified. + /// true if the specified exception is considered as transient; otherwise, + /// false. + bool IsTransient(Exception ex); + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/IncrementalRetryStrategy.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/IncrementalRetryStrategy.cs new file mode 100644 index 0000000000000..6997fe9c648d4 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/IncrementalRetryStrategy.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// A retry strategy with a specified number of retry attempts and an incremental time + /// interval between retries. + /// + public class IncrementalRetryStrategy : RetryStrategy + { + /// + /// Represents the default time increment between retry attempts in the progressive delay policy. + /// + public static readonly TimeSpan DefaultRetryIncrement = TimeSpan.FromSeconds(1.0); + + private readonly TimeSpan _increment; + private readonly TimeSpan _initialInterval; + private readonly int _retryCount; + + /// + /// Initializes a new instance of the class. + /// + public IncrementalRetryStrategy() + : this(DefaultClientRetryCount, DefaultRetryInterval, DefaultRetryIncrement) + { + } + + /// + /// Initializes a new instance of the class with the specified retry settings. + /// + /// The number of retry attempts. + /// The initial interval that will apply for the first retry. + /// The incremental time value that will be used to calculate the progressive + /// delay between retries. + public IncrementalRetryStrategy(int retryCount, TimeSpan initialInterval, TimeSpan increment) + : this(null, retryCount, initialInterval, increment) + { + } + + /// + /// Initializes a new instance of the class with the specified name and retry settings. + /// + /// The retry strategy name. + /// The number of retry attempts. + /// The initial interval that will apply for the first retry. + /// The incremental time value that will be used to calculate the progressive + /// delay between retries. + public IncrementalRetryStrategy(string name, int retryCount, TimeSpan initialInterval, TimeSpan increment) + : this(name, retryCount, initialInterval, increment, DefaultFirstFastRetry) + { + } + + /// + /// Initializes a new instance of the class with the specified number of retry attempts, + /// time interval, retry strategy, and fast start option. + /// + /// The retry strategy name. + /// The number of retry attempts. + /// The initial interval that will apply for the first retry. + /// The incremental time value that will be used to calculate the progressive delay between + /// retries. + /// true to immediately retry in the first attempt; otherwise, false. The subsequent + /// retries will remain subject to the configured retry interval. + public IncrementalRetryStrategy(string name, int retryCount, TimeSpan initialInterval, TimeSpan increment, bool firstFastRetry) + : base(name, firstFastRetry) + { + Guard.ArgumentNotNegativeValue(retryCount, "retryCount"); + Guard.ArgumentNotNegativeValue(initialInterval.Ticks, "initialInterval"); + Guard.ArgumentNotNegativeValue(increment.Ticks, "increment"); + + _retryCount = retryCount; + _initialInterval = initialInterval; + _increment = increment; + } + + /// + /// Returns the corresponding ShouldRetry delegate. + /// + /// The ShouldRetry delegate. + public override ShouldRetryHandler GetShouldRetryHandler() + { + return delegate(int currentRetryCount, Exception lastException) + { + if (currentRetryCount < _retryCount) + { + TimeSpan retryInterval = TimeSpan.FromMilliseconds(_initialInterval.TotalMilliseconds + + (_increment.TotalMilliseconds* + currentRetryCount)); + return new RetryCondition(true, retryInterval); + } + return new RetryCondition(false, TimeSpan.Zero); + }; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryCondition.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryCondition.cs new file mode 100644 index 0000000000000..41f3dbbd4d8b1 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryCondition.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Defines a retry condition. + /// + public class RetryCondition + { + /// Is retry allowed. + /// The delay that indicates how long the current thread will be suspended before. + /// the next iteration is invoked. + public RetryCondition(bool retryAllowed, TimeSpan delay) + { + RetryAllowed = retryAllowed; + DelayBeforeRetry = delay; + } + + /// + /// Gets or sets the retry interval value for retry. + /// + public TimeSpan DelayBeforeRetry { get; set; } + + /// + /// Gets or sets a value indicating whether retry attempt is allowed. + /// + public Boolean RetryAllowed { get; set; } + + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryManager.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryManager.cs new file mode 100644 index 0000000000000..61076d55f84f0 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryManager.cs @@ -0,0 +1,236 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Microsoft.Rest.ClientRuntime.Properties; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Provides the entry point to the retry functionality. + /// + public class RetryManager + { + private static RetryManager _defaultRetryManager; + private readonly IDictionary _defaultRetryStrategiesMap; + private readonly IDictionary _retryStrategies; + private string _defaultRetryStrategyName; + private RetryStrategy _defaultStrategy; + + /// + /// Initializes a new instance of the class. + /// + /// The complete set of retry strategies. + public RetryManager(IEnumerable retryStrategies) + : this(retryStrategies, null, null) + { + } + + /// + /// Initializes a new instance of the class with the specified retry + /// strategies and default retry strategy name. + /// + /// The complete set of retry strategies. + /// The default retry strategy. + public RetryManager(IEnumerable retryStrategies, string defaultRetryStrategyName) + : this(retryStrategies, defaultRetryStrategyName, null) + { + } + + /// + /// Initializes a new instance of the class with the specified retry + /// strategies and defaults. + /// + /// The complete set of retry strategies. + /// The default retry strategy. + /// The names of the default strategies for different technologies. + public RetryManager(IEnumerable retryStrategies, string defaultRetryStrategyName, + IDictionary defaultRetryStrategyNamesMap) + { + _retryStrategies = retryStrategies.ToDictionary(p => p.Name); + DefaultRetryStrategyName = defaultRetryStrategyName; + + _defaultRetryStrategiesMap = new Dictionary(); + if (defaultRetryStrategyNamesMap != null) + { + foreach (var map in defaultRetryStrategyNamesMap.Where(x => !string.IsNullOrWhiteSpace(x.Value))) + { + RetryStrategy strategy; + if (!_retryStrategies.TryGetValue(map.Value, out strategy)) + { + throw new ArgumentOutOfRangeException( + "defaultRetryStrategyNamesMap", + string.Format(CultureInfo.CurrentCulture, Resources.DefaultRetryStrategyMappingNotFound, + map.Key, map.Value)); + } + + _defaultRetryStrategiesMap.Add(map.Key, strategy); + } + } + } + + /// + /// Gets the default for the application. + /// + public static RetryManager Instance + { + get + { + var instance = _defaultRetryManager; + if (instance == null) + { + throw new InvalidOperationException(Resources.ExceptionRetryManagerNotSet); + } + + return instance; + } + } + + /// + /// Gets or sets the default retry strategy name. + /// + public string DefaultRetryStrategyName + { + get { return _defaultRetryStrategyName; } + set + { + if (!string.IsNullOrWhiteSpace(value)) + { + RetryStrategy strategy; + if (_retryStrategies.TryGetValue(value, out strategy)) + { + _defaultRetryStrategyName = value; + _defaultStrategy = strategy; + } + else + { + throw new ArgumentOutOfRangeException("value", string.Format(CultureInfo.CurrentCulture, + Resources.RetryStrategyNotFound, value)); + } + } + else + { + _defaultRetryStrategyName = null; + } + } + } + + /// + /// Sets the specified retry manager as the default retry manager. + /// Will throw an exception if the manager is already set. + /// + /// The retry manager. + public static void SetDefault(RetryManager retryManager) + { + SetDefault(retryManager, true); + } + + /// + /// Sets the specified retry manager as the default retry manager. + /// + /// The retry manager. + /// true to throw an exception if the manager is already set; otherwise, false. + public static void SetDefault(RetryManager retryManager, bool throwIfSet) + { + if (_defaultRetryManager != null && throwIfSet && retryManager != _defaultRetryManager) + { + throw new InvalidOperationException(Resources.ExceptionRetryManagerAlreadySet); + } + + _defaultRetryManager = retryManager; + } + + + /// + /// Returns a retry policy with the specified error detection strategy and the default retry strategy + /// defined in the configuration. + /// + /// The type that implements the + /// interface that is responsible for detecting transient conditions. + /// A new retry policy with the specified error detection strategy and the default retry + /// strategy defined in the configuration. + public virtual RetryPolicy GetRetryPolicy() + where T : ITransientErrorDetectionStrategy, new() + { + return new RetryPolicy(GetRetryStrategy()); + } + + /// + /// Returns a retry policy with the specified error detection strategy and retry strategy. + /// + /// The type that implements the + /// interface that is responsible for detecting transient conditions. + /// The retry strategy name, as defined in the configuration. + /// A new retry policy with the specified error detection strategy and the default retry + /// strategy defined in the configuration. + public virtual RetryPolicy GetRetryPolicy(string retryStrategyName) + where T : ITransientErrorDetectionStrategy, new() + { + return new RetryPolicy(GetRetryStrategy(retryStrategyName)); + } + + /// + /// Returns the default retry strategy defined in the configuration. + /// + /// The retry strategy that matches the default strategy. + public virtual RetryStrategy GetRetryStrategy() + { + return _defaultStrategy; + } + + /// + /// Returns the retry strategy that matches the specified name. + /// + /// The retry strategy name. + /// The retry strategy that matches the specified name. + public virtual RetryStrategy GetRetryStrategy(string retryStrategyName) + { + if (string.IsNullOrEmpty(retryStrategyName)) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.StringCannotBeEmpty, + "retryStrategyName")); + } + + RetryStrategy retryStrategy; + if (!_retryStrategies.TryGetValue(retryStrategyName, out retryStrategy)) + { + throw new ArgumentOutOfRangeException(string.Format(CultureInfo.CurrentCulture, + Resources.RetryStrategyNotFound, retryStrategyName)); + } + + return retryStrategy; + } + + /// + /// Returns the retry strategy for the specified technology. + /// + /// The technology to get the default retry strategy for. + /// The retry strategy for the specified technology. + public virtual RetryStrategy GetDefaultRetryStrategy(string technology) + { + if (string.IsNullOrEmpty(technology)) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.StringCannotBeEmpty, + "technology")); + } + + + RetryStrategy retryStrategy; + if (!_defaultRetryStrategiesMap.TryGetValue(technology, out retryStrategy)) + { + retryStrategy = _defaultStrategy; + } + + if (retryStrategy == null) + { + throw new ArgumentOutOfRangeException(string.Format(CultureInfo.CurrentCulture, + Resources.DefaultRetryStrategyNotFound, technology)); + } + + return retryStrategy; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryPolicy.Generic.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryPolicy.Generic.cs new file mode 100644 index 0000000000000..20515323b1a9a --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryPolicy.Generic.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Provides a generic version of the class. + /// + /// The type that implements the + /// interface that is responsible for detecting transient conditions. + public class RetryPolicy : RetryPolicy where T : ITransientErrorDetectionStrategy, new() + { + /// + /// Initializes a new instance of the class with the specified number of + /// retry attempts and parameters defining the progressive delay between retries. + /// + /// The strategy to use for this retry policy. + public RetryPolicy(RetryStrategy retryStrategy) + : base(new T(), retryStrategy) + { + } + + /// + /// Initializes a new instance of the class with the specified number of + /// retry attempts and the default fixed time interval between retries. + /// + /// The number of retry attempts. + public RetryPolicy(int retryCount) + : base(new T(), retryCount) + { + } + + /// + /// Initializes a new instance of the class with the specified number of + /// retry attempts and a fixed time interval between retries. + /// + /// The number of retry attempts. + /// The interval between retries. + public RetryPolicy(int retryCount, TimeSpan retryInterval) + : base(new T(), retryCount, retryInterval) + { + } + + /// + /// Initializes a new instance of the class with the specified number of + /// retry attempts and backoff parameters for calculating the exponential delay between retries. + /// + /// The number of retry attempts. + /// The minimum backoff time. + /// The maximum backoff time. + /// The time value that will be used to calculate a random delta in the + /// exponential delay between retries. + public RetryPolicy(int retryCount, TimeSpan minBackoff, TimeSpan maxBackoff, TimeSpan deltaBackoff) + : base(new T(), retryCount, minBackoff, maxBackoff, deltaBackoff) + { + } + + /// + /// Initializes a new instance of the class with the specified number of + /// retry attempts and parameters defining the progressive delay between retries. + /// + /// The number of retry attempts. + /// The initial interval that will apply for the first retry. + /// The incremental time value that will be used to calculate the progressive delay + /// between retries. + public RetryPolicy(int retryCount, TimeSpan initialInterval, TimeSpan increment) + : base(new T(), retryCount, initialInterval, increment) + { + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryPolicy.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryPolicy.cs new file mode 100644 index 0000000000000..a2d606720528a --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryPolicy.cs @@ -0,0 +1,280 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Provides the base implementation of the retry mechanism for unreliable actions and + /// transient conditions. + /// + public class RetryPolicy + { + /// + /// Initializes a new instance of the class with the specified number of retry + /// attempts and parameters defining the progressive delay between retries. + /// + /// The that is + /// responsible for detecting transient conditions. + /// The strategy to use for this retry policy. + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, RetryStrategy retryStrategy) + { + Guard.ArgumentNotNull(errorDetectionStrategy, "errorDetectionStrategy"); + Guard.ArgumentNotNull(retryStrategy, "retryPolicy"); + + this.ErrorDetectionStrategy = errorDetectionStrategy; + + if (errorDetectionStrategy == null) + { + throw new InvalidOperationException(ClientRuntime.Properties.Resources.ITransientErrorDetectionStrategyNotImplemented); + } + + this.RetryStrategy = retryStrategy; + } + + /// + /// Initializes a new instance of the class with the specified number of retry + /// attempts and default fixed time interval between retries. + /// + /// The that is responsible + /// for detecting transient conditions. + /// The number of retry attempts. + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount) + : this(errorDetectionStrategy, new FixedIntervalRetryStrategy(retryCount)) + { + } + + /// + /// Initializes a new instance of the class with the specified number of retry + /// attempts and fixed time interval between retries. + /// + /// The that is responsible + /// for detecting transient conditions. + /// The number of retry attempts. + /// The interval between retries. + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan retryInterval) + : this(errorDetectionStrategy, new FixedIntervalRetryStrategy(retryCount, retryInterval)) + { + } + + /// + /// Initializes a new instance of the class with the specified number of retry attempts + /// and backoff parameters for calculating the exponential delay between retries. + /// + /// The that is responsible + /// for detecting transient conditions. + /// The number of retry attempts. + /// The minimum backoff time. + /// The maximum backoff time. + /// The time value that will be used to calculate a random delta in the exponential delay + /// between retries. + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan minBackoff, + TimeSpan maxBackoff, TimeSpan deltaBackoff) + : this(errorDetectionStrategy, new ExponentialBackoffRetryStrategy(retryCount, minBackoff, maxBackoff, deltaBackoff)) + { + } + + /// + /// Initializes a new instance of the class with the specified number of retry + /// attempts and parameters defining the progressive delay between retries. + /// + /// The that is responsible for + /// detecting transient conditions. + /// The number of retry attempts. + /// The initial interval that will apply for the first retry. + /// The incremental time value that will be used to calculate the progressive delay between + /// retries. + public RetryPolicy(ITransientErrorDetectionStrategy errorDetectionStrategy, int retryCount, TimeSpan initialInterval, + TimeSpan increment) + : this(errorDetectionStrategy, new IncrementalRetryStrategy(retryCount, initialInterval, increment)) + { + } + + /// + /// An instance of a callback delegate that will be invoked whenever a retry condition is encountered. + /// + public event EventHandler Retrying; + + /// + /// Gets the retry strategy. + /// + public RetryStrategy RetryStrategy { get; private set; } + + /// + /// Gets the instance of the error detection strategy. + /// + public ITransientErrorDetectionStrategy ErrorDetectionStrategy { get; private set; } + + /// + /// Repetitively executes the specified action while it satisfies the current retry policy. + /// + /// A delegate that represents the executable action that doesn't return any results. + public virtual void ExecuteAction(Action action) + { + Guard.ArgumentNotNull(action, "action"); + + this.ExecuteAction(() => { action(); return default(object); }); + } + + /// + /// Repetitively executes the specified action while it satisfies the current retry policy. + /// + /// The type of result expected from the executable action. + /// A delegate that represents the executable action that returns the result of + /// type . + /// The result from the action. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", + "CA1062:Validate arguments of public methods", + MessageId = "0", Justification = "Validated with Guard")] + public virtual TResult ExecuteAction(Func func) + { + Guard.ArgumentNotNull(func, "func"); + + int retryCount = 0; + TimeSpan delay = TimeSpan.Zero; + Exception lastError; + + var shouldRetry = this.RetryStrategy.GetShouldRetryHandler(); + + for (;;) + { + lastError = null; + + try + { + return func(); + } + catch (Exception ex) + { + lastError = ex; + + if (!(this.ErrorDetectionStrategy.IsTransient(lastError))) + { + throw; + } + + RetryCondition condition = shouldRetry(retryCount++, lastError); + if (!condition.RetryAllowed){ + throw; + } + delay = condition.DelayBeforeRetry; + } + + // Perform an extra check in the delay interval. Should prevent from accidentally ending up with the + // value of -1 that will block a thread indefinitely. In addition, any other negative numbers will + // cause an ArgumentOutOfRangeException fault that will be thrown by Thread.Sleep. + if (delay.TotalMilliseconds < 0) + { + delay = TimeSpan.Zero; + } + + this.OnRetrying(retryCount, lastError, delay); + + if (retryCount > 1 || !this.RetryStrategy.FastFirstRetry) + { + Task.Delay(delay).Wait(); + } + } + } + + /// + /// Repetitively executes the specified asynchronous task while it satisfies the current retry policy. + /// + /// A function that returns a started task (also known as "hot" task). + /// + /// A task that will run to completion if the original task completes successfully (either the + /// first time or after retrying transient failures). If the task fails with a non-transient error or + /// the retry limit is reached, the returned task will transition to a faulted state and the exception must be observed. + /// + public Task ExecuteAsync(Func taskAction) + { + return this.ExecuteAsync(taskAction, default(CancellationToken)); + } + + /// + /// Repetitively executes the specified asynchronous task while it satisfies the current retry policy. + /// + /// A function that returns a started task (also known as "hot" task). + /// The token used to cancel the retry operation. This token does not cancel + /// the execution of the asynchronous task. + /// + /// Returns a task that will run to completion if the original task completes successfully (either the + /// first time or after retrying transient failures). If the task fails with a non-transient error or + /// the retry limit is reached, the returned task will transition to a faulted state and the exception must be observed. + /// + public Task ExecuteAsync(Func taskAction, CancellationToken cancellationToken) + { + if (taskAction == null) throw new ArgumentNullException("taskAction"); + + return new AsyncExecution( + taskAction, + this.RetryStrategy.GetShouldRetryHandler(), + this.ErrorDetectionStrategy.IsTransient, + this.OnRetrying, + this.RetryStrategy.FastFirstRetry, + cancellationToken) + .ExecuteAsync(); + } + + /// + /// Repeatedly executes the specified asynchronous task while it satisfies the current retry policy. + /// + /// A function that returns a started task (also known as "hot" task). + /// + /// Returns a task that will run to completion if the original task completes successfully (either the + /// first time or after retrying transient failures). If the task fails with a non-transient error or + /// the retry limit is reached, the returned task will transition to a faulted state and the exception + /// must be observed. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public Task ExecuteAsync(Func> taskFunc) + { + return this.ExecuteAsync(taskFunc, default(CancellationToken)); + } + + /// + /// Repeatedly executes the specified asynchronous task while it satisfies the current retry policy. + /// + /// A function that returns a started task (also known as "hot" task). + /// The token used to cancel the retry operation. This token does not + /// cancel the execution of the asynchronous task. + /// + /// Returns a task that will run to completion if the original task completes successfully (either the + /// first time or after retrying transient failures). If the task fails with a non-transient error or + /// the retry limit is reached, the returned task will transition to a faulted state and the exception must + /// be observed. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] + public Task ExecuteAsync(Func> taskFunc, CancellationToken cancellationToken) + { + if (taskFunc == null) throw new ArgumentNullException("taskFunc"); + + return new AsyncExecution( + taskFunc, + this.RetryStrategy.GetShouldRetryHandler(), + this.ErrorDetectionStrategy.IsTransient, + this.OnRetrying, + this.RetryStrategy.FastFirstRetry, + cancellationToken) + .ExecuteAsync(); + } + + /// + /// Notifies the subscribers whenever a retry condition is encountered. + /// + /// The current retry attempt count. + /// The exception that caused the retry conditions to occur. + /// The delay that indicates how long the current thread will be suspended before + /// the next iteration is invoked. + protected virtual void OnRetrying(int retryCount, Exception lastError, TimeSpan delay) + { + if (this.Retrying != null) + { + this.Retrying(this, new RetryingEventArgs(retryCount, delay, lastError)); + } + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryStrategy.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryStrategy.cs new file mode 100644 index 0000000000000..a4ade9a11eb45 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryStrategy.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Defines a callback delegate that will be invoked whenever a retry condition is encountered. + /// + /// The current retry attempt count. + /// The exception that caused the retry conditions to occur. + /// A retry condition instance + public delegate RetryCondition ShouldRetryHandler(int retryCount, Exception lastException); + + /// + /// Represents a retry strategy that determines the number of retry attempts and the interval + /// between retries. + /// + public abstract class RetryStrategy + { + /// + /// Represents the default number of retry attempts. + /// + public static readonly int DefaultClientRetryCount = 10; + + /// + /// Represents the default interval between retries. + /// + public static readonly TimeSpan DefaultRetryInterval = TimeSpan.FromSeconds(1.0); + + /// + /// Represents the default flag indicating whether the first retry attempt will be made immediately, + /// whereas subsequent retries will remain subject to the retry interval. + /// + public static readonly bool DefaultFirstFastRetry = true; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the retry strategy. + /// true to immediately retry in the first attempt; otherwise, false. + /// The subsequent retries will remain subject to the configured retry interval. + protected RetryStrategy(string name, bool firstFastRetry) + { + this.Name = name; + this.FastFirstRetry = firstFastRetry; + } + + /// + /// Gets or sets a value indicating whether the first retry attempt will be made immediately, + /// whereas subsequent retries will remain subject to the retry interval. + /// + public bool FastFirstRetry { get; set; } + + /// + /// Gets the name of the retry strategy. + /// + public string Name { get; private set; } + + /// + /// Returns the corresponding ShouldRetry delegate. + /// + /// The ShouldRetry delegate. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "Getter not appropriate for returning new delegate instance for each call.")] + public abstract ShouldRetryHandler GetShouldRetryHandler(); + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryingEventArgs.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryingEventArgs.cs new file mode 100644 index 0000000000000..6356085040bf9 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/RetryingEventArgs.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Contains information that is required for the event. + /// + public class RetryingEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The current retry attempt count. + /// The delay that indicates how long the current thread will be + /// suspended before the next iteration is invoked. + /// The exception that caused the retry conditions to occur. + public RetryingEventArgs(int currentRetryCount, TimeSpan delay, Exception lastException) + { + Guard.ArgumentNotNull(lastException, "lastException"); + + CurrentRetryCount = currentRetryCount; + Delay = delay; + LastException = lastException; + } + + /// + /// Gets the current retry count. + /// + public int CurrentRetryCount { get; private set; } + + /// + /// Gets the delay that indicates how long the current thread will be suspended before the + /// next iteration is invoked. + /// + public TimeSpan Delay { get; private set; } + + /// + /// Gets the exception that caused the retry conditions to occur. + /// + public Exception LastException { get; private set; } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/TransientErrorIgnoreStrategy.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/TransientErrorIgnoreStrategy.cs new file mode 100644 index 0000000000000..ec27f629b4ea6 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TransientFaultHandling/TransientErrorIgnoreStrategy.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Rest.TransientFaultHandling +{ + /// + /// Retry strategy that ignores any transient errors. + /// + public class TransientErrorIgnoreStrategy : ITransientErrorDetectionStrategy + { + /// + /// Always returns false. + /// + /// The exception. + /// Always false. + public bool IsTransient(Exception ex) + { + return false; + } + } +} diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TypeConversion.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TypeConversion.cs new file mode 100644 index 0000000000000..b1d81e22bcdf4 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/TypeConversion.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Text; + +namespace Microsoft.Rest +{ + /// + /// Static conversion utility methods. + /// + public static class TypeConversion + { + /// + /// Converts a string to a UTF8-encoded string. + /// + /// The string of base-64 digits to convert. + /// The UTF8-encoded string. + public static string FromBase64String(string value) + { + byte[] bytes = Convert.FromBase64String(value); + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + + /// + /// Uses Uri.TryCreate to parse a string as a relative or absolute Uri. + /// + /// The string to parse. + /// Uri or null. + public static Uri TryParseUri(string value) + { + if (!string.IsNullOrEmpty(value)) + { + Uri uri; + if (Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out uri)) + { + return uri; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ValidationException.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ValidationException.cs new file mode 100644 index 0000000000000..b6603190d40ab --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ValidationException.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Runtime.Serialization; +#if !PORTABLE +using System.Security.Permissions; +#endif + +namespace Microsoft.Rest +{ + /// + /// Validation exception for Microsoft Rest Client. + /// +#if !PORTABLE + [Serializable] +#endif + public class ValidationException : RestException + { + /// + /// Gets validation rule. + /// + public string Rule { get; private set; } + + /// + /// Gets validation target. + /// + public string Target { get; private set; } + + /// + /// Gets validation details. + /// + public object Details { get; private set; } + + /// + /// Initializes a new instance of the ValidationException class. + /// + public ValidationException() + { + } + + /// + /// Initializes a new instance of the ValidationException class. + /// + /// Exception message. + public ValidationException(string message) + : base(message, null) + { + } + + /// + /// Initializes a new instance of the ValidationException class. + /// + /// Validation rule. + /// Target of the validation. + public ValidationException(string rule, string target) + : base(string.Format(CultureInfo.InvariantCulture, "'{0}' {1}.", target, rule), null) + { + Rule = rule; + Target = target; + } + + /// + /// Initializes a new instance of the ValidationException class. + /// + /// Validation rule. + /// Target of the validation. + /// Validation details. + public ValidationException(string rule, string target, object details) + : base(string.Format(CultureInfo.InvariantCulture, "'{0}' {1} '{2}'.", target, rule, details), null) + { + Rule = rule; + Target = target; + Details = details; + } + + /// + /// Initializes a new instance of the ValidationException class. + /// + /// The exception message. + /// Inner exception. + public ValidationException(string message, Exception innerException) + : base(message, innerException) + { + } + +#if !PORTABLE + /// + /// Initializes a new instance of the ValidationException class. + /// + /// Serialization info. + /// Streaming context. + protected ValidationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + /// + /// Serializes content of the exception. + /// + /// Serialization info. + /// Streaming context. + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + if (info == null) + { + throw new ArgumentNullException("info"); + } + + info.AddValue("Rule", Rule); + info.AddValue("Target", Target); + info.AddValue("Details", Details); + } +#endif + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ValidationRules.cs b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ValidationRules.cs new file mode 100644 index 0000000000000..f0a544f24a8ef --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/ValidationRules.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Rest.ClientRuntime.Properties; + +namespace Microsoft.Rest +{ + /// + /// Defines set of validation rules. + /// + public static class ValidationRules + { + public static readonly string CannotBeNull = Resources.ValidationCannotBeNull; + public static readonly string InclusiveMaximum = Resources.ValidationMaximum; + public static readonly string ExclusiveMaximum = Resources.ValidationExclusiveMaximum; + public static readonly string MaxLength = Resources.ValidationMaximumLength; + public static readonly string MinLength = Resources.ValidationMinimumLength; + public static readonly string Pattern = Resources.ValidationPattern; + public static readonly string MaxItems = Resources.ValidationMaximumItems; + public static readonly string MinItems = Resources.ValidationMinimumItems; + public static readonly string UniqueItems = Resources.ValidationUniqueItems; + public static readonly string Enum = Resources.ValidationEnum; + public static readonly string MultipleOf = Resources.ValidationMultipleOf; + public static readonly string InclusiveMinimum = Resources.ValidationMinimum; + public static readonly string ExclusiveMinimum = Resources.ValidationExclusiveMinimum; + } +} \ No newline at end of file diff --git a/src/ClientRuntime/Microsoft.Rest.ClientRuntime/project.json b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/project.json new file mode 100644 index 0000000000000..0172a322e53b1 --- /dev/null +++ b/src/ClientRuntime/Microsoft.Rest.ClientRuntime/project.json @@ -0,0 +1,88 @@ +{ + "version": "2.3.2", + "copyright": "Copyright (c) Microsoft Corporation", + "title": "Client Runtime Library for Microsoft AutoRest Generated Clients", + "description": "Infrastructure for error handling, tracing, and HttpClient pipeline configuration. Required by client libraries generated using AutoRest. \nSupported Platforms:\n - Portable Class Libraries\n - .NET Framework 4.5\n - Windows 8\n - Windows Phone 8.1\n - DotNet Core", + "authors": [ "Microsoft" ], + + "packOptions": { + "summary": "Provides HttpClient infrastructure for clients generated by AutoRest.", + "iconUrl": "https://cdn.rawgit.com/Azure/AutoRest/7c1576dfb56974176223545cfac5762d168ded5f/Documentation/images/autorest-small-flat.png", + "projectUrl": "https://github.com/Azure/AutoRest", + "licenseUrl": "https://raw.githubusercontent.com/Microsoft/dotnet/master/LICENSE", + "tags": [ "Microsoft AutoRest ClientRuntime REST" ], + "requireLicenseAcceptance": true + }, + + "buildOptions": { + "delaySign": true, + "publicSign": false, + "keyFile": "../../../tools/MSSharedLibKey.snk" + }, + + "frameworks": { + "net45": { + "buildOptions": { "define": [ "net45" ] }, + "frameworkAssemblies": { + "mscorlib": "", + "System": "", + "System.Net": "", + "System.Net.Http": "", + "System.Net.Http.WebRequest": "", + "System.Runtime.Serialization": "", + "System.Xml": "" + }, + "dependencies": { + "Newtonsoft.Json": "6.0.8" + } + }, + "netstandard1.1": { + "buildOptions": { "define": [ "PORTABLE" ] }, + "imports": ["dnxcore50", "portable-net45+win8"], + "dependencies": { + "Newtonsoft.Json": "[9.0.1,10.0)", + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.Diagnostics.Tracing": "4.1.0", + "System.Globalization": "4.0.11", + "System.Linq": "4.1.0", + "System.Linq.Expressions": "4.1.0", + "System.Net.Http": "4.1.0", + "System.Net.Primitives": "4.0.11", + "System.ObjectModel": "4.0.12", + "System.Reflection.Extensions": "4.0.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime.Extensions": "4.1.0", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11" + } + }, + "netstandard1.5": { + "buildOptions": { "define": [ "PORTABLE" ] }, + "imports": [ "dnxcore50" ], + "dependencies": { + "Newtonsoft.Json": "[9.0.1,10.0)", + "Microsoft.NETCore.Platforms": "1.0.1", + "System.Collections": "4.0.11", + "System.Diagnostics.Debug": "4.0.11", + "System.Diagnostics.Tools": "4.0.1", + "System.Diagnostics.Tracing": "4.1.0", + "System.Globalization": "4.0.11", + "System.Linq": "4.1.0", + "System.Linq.Expressions": "4.1.0", + "System.Net.Http": "4.1.0", + "System.Net.Primitives": "4.0.11", + "System.ObjectModel": "4.0.12", + "System.Reflection.Extensions": "4.0.1", + "System.Resources.ResourceManager": "4.0.1", + "System.Runtime.Extensions": "4.1.0", + "System.Text.RegularExpressions": "4.1.0", + "System.Threading": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11" + } + } + } +} diff --git a/src/ClientRuntime/global.json b/src/ClientRuntime/global.json new file mode 100644 index 0000000000000..25fb11a0a6d5f --- /dev/null +++ b/src/ClientRuntime/global.json @@ -0,0 +1,5 @@ +{ + "projects": [ "Microsoft.Rest.ClientRuntime", "Microsoft.Rest.ClientRuntime.Azure.Authentication", "Microsoft.Rest.ClientRuntime.Etw", + "Microsoft.Rest.ClientRuntime.Tests", "Microsoft.Rest.ClientRuntime.Azure", "Microsoft.Rest.ClientRuntime.Azure.Tests", + "Microsoft.Rest.ClientRuntime.Log4Net", "Microsoft.Rest.ClientRuntime.Tracing.Tests" ] +} \ No newline at end of file diff --git a/src/KeyVault/global.json b/src/KeyVault/global.json index 76bb51445ecf9..6184e861d3229 100644 --- a/src/KeyVault/global.json +++ b/src/KeyVault/global.json @@ -1,5 +1,6 @@ { "projects": [ + "../ClientRuntime", "Microsoft.Azure.KeyVault", "Microsoft.Azure.KeyVault.Core", "Microsoft.Azure.KeyVault.Cryptography", diff --git a/src/ResourceManagement/Authorization/global.json b/src/ResourceManagement/Authorization/global.json index 821021702cea7..b0cbb459bc03c 100644 --- a/src/ResourceManagement/Authorization/global.json +++ b/src/ResourceManagement/Authorization/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework" ] + "projects": [ "../../ClientRuntime", "../../TestFramework" ] } \ No newline at end of file diff --git a/src/ResourceManagement/Batch/global.json b/src/ResourceManagement/Batch/global.json index fa12c42ec3c22..44ec2e7f8f32a 100644 --- a/src/ResourceManagement/Batch/global.json +++ b/src/ResourceManagement/Batch/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework/", "BatchManagement", "Batch.Tests" ] + "projects": [ "../../ClientRuntime", "../../TestFramework/", "BatchManagement", "Batch.Tests" ] } \ No newline at end of file diff --git a/src/ResourceManagement/Cdn/global.json b/src/ResourceManagement/Cdn/global.json index c23e3caf67c26..91166a2215055 100644 --- a/src/ResourceManagement/Cdn/global.json +++ b/src/ResourceManagement/Cdn/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework/", "Microsoft.Azure.Management.Cdn" ] + "projects": [ "../../ClientRuntime", "../../TestFramework/", "Microsoft.Azure.Management.Cdn" ] } \ No newline at end of file diff --git a/src/ResourceManagement/CognitiveServices/global.json b/src/ResourceManagement/CognitiveServices/global.json index 7c480bc0f491b..97730c371666a 100644 --- a/src/ResourceManagement/CognitiveServices/global.json +++ b/src/ResourceManagement/CognitiveServices/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework/", "Microsoft.Azure.Management.CognitiveServices" ] + "projects": [ "../../ClientRuntime", "./../TestFramework/", "Microsoft.Azure.Management.CognitiveServices" ] } \ No newline at end of file diff --git a/src/ResourceManagement/Compute/global.json b/src/ResourceManagement/Compute/global.json index 0889335db4e46..e0fb4d211f30e 100644 --- a/src/ResourceManagement/Compute/global.json +++ b/src/ResourceManagement/Compute/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.Compute", "Compute.Tests", "../Network" ] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.Compute", "Compute.Tests", "../Network" ] } diff --git a/src/ResourceManagement/DataLake.Analytics/global.json b/src/ResourceManagement/DataLake.Analytics/global.json index 1d0f093308344..93865f30f855b 100644 --- a/src/ResourceManagement/DataLake.Analytics/global.json +++ b/src/ResourceManagement/DataLake.Analytics/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.DataLake.Analytics", "DataLakeAnalytics.Tests", "../DataLake.Store" ] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.DataLake.Analytics", "DataLakeAnalytics.Tests", "../DataLake.Store" ] } diff --git a/src/ResourceManagement/DataLake.Store/global.json b/src/ResourceManagement/DataLake.Store/global.json index 8adab266f882d..3a2a3bc3486c0 100644 --- a/src/ResourceManagement/DataLake.Store/global.json +++ b/src/ResourceManagement/DataLake.Store/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.DataLake.Store", "DataLakeStore.Tests" ] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.DataLake.Store", "DataLakeStore.Tests" ] } diff --git a/src/ResourceManagement/DevTestLabs/global.json b/src/ResourceManagement/DevTestLabs/global.json index 821021702cea7..b0cbb459bc03c 100644 --- a/src/ResourceManagement/DevTestLabs/global.json +++ b/src/ResourceManagement/DevTestLabs/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework" ] + "projects": [ "../../ClientRuntime", "../../TestFramework" ] } \ No newline at end of file diff --git a/src/ResourceManagement/Dns/global.json b/src/ResourceManagement/Dns/global.json index 84ec73ec02aeb..0b5499253321b 100644 --- a/src/ResourceManagement/Dns/global.json +++ b/src/ResourceManagement/Dns/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.Dns", "Dns.Tests"] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.Dns", "Dns.Tests"] } \ No newline at end of file diff --git a/src/ResourceManagement/EventHub/global.json b/src/ResourceManagement/EventHub/global.json index 307eb29804541..4a82bf30890b6 100644 --- a/src/ResourceManagement/EventHub/global.json +++ b/src/ResourceManagement/EventHub/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework/", "Microsoft.Azure.Management.ServiceBus" ] + "projects": [ "../../ClientRuntime/", "../../TestFramework/", "Microsoft.Azure.Management.ServiceBus" ] } \ No newline at end of file diff --git a/src/ResourceManagement/Graph.RBAC/global.json b/src/ResourceManagement/Graph.RBAC/global.json index 821021702cea7..b0cbb459bc03c 100644 --- a/src/ResourceManagement/Graph.RBAC/global.json +++ b/src/ResourceManagement/Graph.RBAC/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework" ] + "projects": [ "../../ClientRuntime", "../../TestFramework" ] } \ No newline at end of file diff --git a/src/ResourceManagement/Insights/global.json b/src/ResourceManagement/Insights/global.json index 106480c0afca9..e46e19e70768c 100644 --- a/src/ResourceManagement/Insights/global.json +++ b/src/ResourceManagement/Insights/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Insights", "Insights.Tests"] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Insights", "Insights.Tests"] } diff --git a/src/ResourceManagement/IotHub/global.json b/src/ResourceManagement/IotHub/global.json index 0a84f1c0ca30b..5410a97de977c 100644 --- a/src/ResourceManagement/IotHub/global.json +++ b/src/ResourceManagement/IotHub/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.IotHub", "IotHub.Tests"] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.IotHub", "IotHub.Tests"] } diff --git a/src/ResourceManagement/KeyVaultManagement/global.json b/src/ResourceManagement/KeyVaultManagement/global.json index f5e2ee7da852e..1785f14ebf9d3 100644 --- a/src/ResourceManagement/KeyVaultManagement/global.json +++ b/src/ResourceManagement/KeyVaultManagement/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework/", "Microsoft.Azure.Management.KeyVault", "KeyVaultManagement.Tests"] + "projects": [ "../../ClientRuntime", "../../TestFramework/", "Microsoft.Azure.Management.KeyVault", "KeyVaultManagement.Tests"] } \ No newline at end of file diff --git a/src/ResourceManagement/Logic/global.json b/src/ResourceManagement/Logic/global.json index 6eb07dc763465..ea5760fee58a9 100644 --- a/src/ResourceManagement/Logic/global.json +++ b/src/ResourceManagement/Logic/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework/", "Microsoft.Azure.Management.Logic" ] + "projects": [ "../../ClientRuntime", "../../TestFramework/", "Microsoft.Azure.Management.Logic" ] } \ No newline at end of file diff --git a/src/ResourceManagement/MachineLearning/global.json b/src/ResourceManagement/MachineLearning/global.json index 941b0efe32722..1bc1c90f6573d 100644 --- a/src/ResourceManagement/MachineLearning/global.json +++ b/src/ResourceManagement/MachineLearning/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.MachineLearning", "MachineLearning.Tests"] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.MachineLearning", "MachineLearning.Tests"] } \ No newline at end of file diff --git a/src/ResourceManagement/Media/global.json b/src/ResourceManagement/Media/global.json index c1217473d3ed3..3cf62874aef32 100644 --- a/src/ResourceManagement/Media/global.json +++ b/src/ResourceManagement/Media/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework/", "Microsoft.Azure.Management.Media", "Media.Tests" ] + "projects": [ "../../ClientRuntime", "../../TestFramework/", "Microsoft.Azure.Management.Media", "Media.Tests" ] } \ No newline at end of file diff --git a/src/ResourceManagement/Network/global.json b/src/ResourceManagement/Network/global.json index 216a8d595d617..aff94ad58abae 100644 --- a/src/ResourceManagement/Network/global.json +++ b/src/ResourceManagement/Network/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.Network", "Network.Tests" ] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.Network", "Network.Tests" ] } diff --git a/src/ResourceManagement/NotificationHubs/global.json b/src/ResourceManagement/NotificationHubs/global.json index c7e9f0225f7be..55b997d404887 100644 --- a/src/ResourceManagement/NotificationHubs/global.json +++ b/src/ResourceManagement/NotificationHubs/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework/", "Microsoft.Azure.Management.NotificationHubs" ] + "projects": [ "../../ClientRuntime", "../../TestFramework/", "Microsoft.Azure.Management.NotificationHubs" ] } \ No newline at end of file diff --git a/src/ResourceManagement/PowerBIEmbedded/global.json b/src/ResourceManagement/PowerBIEmbedded/global.json index 1cc4eab330aae..ece27691ad03d 100644 --- a/src/ResourceManagement/PowerBIEmbedded/global.json +++ b/src/ResourceManagement/PowerBIEmbedded/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.PowerBIEmbedded", "PowerBIEmbedded.Tests" ] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.PowerBIEmbedded", "PowerBIEmbedded.Tests" ] } \ No newline at end of file diff --git a/src/ResourceManagement/RedisCache/global.json b/src/ResourceManagement/RedisCache/global.json index 25b2fc0718106..7d6568f7b860d 100644 --- a/src/ResourceManagement/RedisCache/global.json +++ b/src/ResourceManagement/RedisCache/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.Redis", "AzureRedisCache.Tests" ] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.Redis", "AzureRedisCache.Tests" ] } \ No newline at end of file diff --git a/src/ResourceManagement/Resource/global.json b/src/ResourceManagement/Resource/global.json index b406012c0ccf4..0bcabff9bee5a 100644 --- a/src/ResourceManagement/Resource/global.json +++ b/src/ResourceManagement/Resource/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.ResourceManager", "Resource.Tests" ] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.ResourceManager", "Resource.Tests" ] } diff --git a/src/ResourceManagement/Scheduler/global.json b/src/ResourceManagement/Scheduler/global.json index db5c483400589..79810abf5f60c 100644 --- a/src/ResourceManagement/Scheduler/global.json +++ b/src/ResourceManagement/Scheduler/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.Scheduler"] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.Scheduler"] } \ No newline at end of file diff --git a/src/ResourceManagement/ServerManagement/global.json b/src/ResourceManagement/ServerManagement/global.json index 9d200385e0d2d..e63e9cc0938a1 100644 --- a/src/ResourceManagement/ServerManagement/global.json +++ b/src/ResourceManagement/ServerManagement/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.ServerManagement" ] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.ServerManagement" ] } \ No newline at end of file diff --git a/src/ResourceManagement/ServiceBus/global.json b/src/ResourceManagement/ServiceBus/global.json index 307eb29804541..37a211341d5bc 100644 --- a/src/ResourceManagement/ServiceBus/global.json +++ b/src/ResourceManagement/ServiceBus/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework/", "Microsoft.Azure.Management.ServiceBus" ] + "projects": [ "../../ClientRuntime", "../../TestFramework/", "Microsoft.Azure.Management.ServiceBus" ] } \ No newline at end of file diff --git a/src/ResourceManagement/Storage/global.json b/src/ResourceManagement/Storage/global.json index 15d7b18c2aafc..36d586f716ec1 100644 --- a/src/ResourceManagement/Storage/global.json +++ b/src/ResourceManagement/Storage/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework/", "Microsoft.Azure.Management.Storage" ] + "projects": [ "../../ClientRuntime", "../../TestFramework/", "Microsoft.Azure.Management.Storage" ] } \ No newline at end of file diff --git a/src/ResourceManagement/TrafficManager/global.json b/src/ResourceManagement/TrafficManager/global.json index 899f9a1dbb86c..fb429c0a827b3 100644 --- a/src/ResourceManagement/TrafficManager/global.json +++ b/src/ResourceManagement/TrafficManager/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework/", "Microsoft.Azure.Management.TrafficManager" ] + "projects": [ "../../ClientRuntime", "../../TestFramework/", "Microsoft.Azure.Management.TrafficManager" ] } \ No newline at end of file diff --git a/src/ResourceManagement/WebSite/global.json b/src/ResourceManagement/WebSite/global.json index 20545559d9821..10860aa473918 100644 --- a/src/ResourceManagement/WebSite/global.json +++ b/src/ResourceManagement/WebSite/global.json @@ -1,3 +1,3 @@ { - "projects": [ "../../TestFramework", "Microsoft.Azure.Management.Websites", "Websites.Tests" ] + "projects": [ "../../ClientRuntime", "../../TestFramework", "Microsoft.Azure.Management.Websites", "Websites.Tests" ] } \ No newline at end of file diff --git a/src/Search/global.json b/src/Search/global.json index feef28d54b486..48db94021bee9 100644 --- a/src/Search/global.json +++ b/src/Search/global.json @@ -1,3 +1,3 @@ { - "projects": [ "Microsoft.Azure.Search", "Microsoft.Azure.Management.Search", "Search.Management.Tests", "Search.Tests" ] + "projects": [ "../ClientRuntime", "Microsoft.Azure.Search", "Microsoft.Azure.Management.Search", "Search.Management.Tests", "Search.Tests" ] } \ No newline at end of file diff --git a/src/TestFramework/global.json b/src/TestFramework/global.json index 012e710aa49eb..868740ac9e495 100644 --- a/src/TestFramework/global.json +++ b/src/TestFramework/global.json @@ -1,3 +1,3 @@ { - "projects": [ "Microsoft.Rest.ClientRuntime.Azure.TestFramework", "Microsoft.Azure.Test.HttpRecorder" ] + "projects": [ "../ClientRuntime", "Microsoft.Rest.ClientRuntime.Azure.TestFramework", "Microsoft.Azure.Test.HttpRecorder" ] } \ No newline at end of file