diff --git a/AspNet_k.sln b/AspNet_k.sln index dd3ef765b..a747325f1 100644 --- a/AspNet_k.sln +++ b/AspNet_k.sln @@ -1,6 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 +VisualStudioVersion = 14.0.22512.0 VisualStudioVersion = 14.0.22122.0 VisualStudioVersion = 14.0.22807.0 MinimumVisualStudioVersion = 10.0.40219.1 @@ -13,6 +14,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json Nuget.config = Nuget.config EndProjectSection + ProjectSection(FolderGlobals) = preProject + global_1json__JSONSchema = http://json.schemastore.org/global + EndProjectSection EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.WindowsAzure.Storage", "Lib\AspNet\Microsoft.WindowsAzure.Storage\Microsoft.WindowsAzure.Storage.xproj", "{10851280-F6EA-4988-80B0-4B0CAEE23196}" EndProject diff --git a/BuildAspNetK.cmd b/BuildAspNetK.cmd index 290022b6b..1302e19cc 100644 --- a/BuildAspNetK.cmd +++ b/BuildAspNetK.cmd @@ -1,8 +1,8 @@ pushd %~dp0 -call Tools\nuget.exe install dnx-coreclr-win-x86 -Version 1.0.0-beta7 -Prerelease -call Tools\nuget.exe install dnx-clr-win-x86 -Version 1.0.0-beta7 -Prerelease -call dnx-coreclr-win-x86.1.0.0-beta7\bin\dnu restore -call dnx-clr-win-x86.1.0.0-beta7\bin\dnu restore +call Tools\nuget.exe install dnx-coreclr-win-x86 -Version 1.0.0-beta8 -Prerelease +call Tools\nuget.exe install dnx-clr-win-x86 -Version 1.0.0-beta8 -Prerelease +call dnx-coreclr-win-x86.1.0.0-beta8\bin\dnu restore +call dnx-clr-win-x86.1.0.0-beta8\bin\dnu restore cd Lib\AspNet\Microsoft.WindowsAzure.Storage -call ..\..\..\dnx-coreclr-win-x86.1.0.0-beta7\bin\dnu build --configuration release +call ..\..\..\dnx-coreclr-win-x86.1.0.0-beta8\bin\dnu build --configuration release popd diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs index 5d7c5f856..249f415f0 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/AssemblyInfo.cs @@ -33,8 +33,8 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] -[assembly: AssemblyInformationalVersion("6.0.1.0-preview")] +[assembly: AssemblyVersion("6.1.1.0")] +[assembly: AssemblyFileVersion("6.1.1.0")] +[assembly: AssemblyInformationalVersion("6.1.1.0-preview")] [assembly: InternalsVisibleTo("Microsoft.WindowsAzure.Storage.Test")] diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec index 1efe416de..23b080b6b 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/WindowsAzure.StorageK.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 6.0.1-preview + 6.1.1-preview Windows Azure Storage Microsoft Microsoft diff --git a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json index e2da38cfa..a15312153 100644 --- a/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json +++ b/Lib/AspNet/Microsoft.WindowsAzure.Storage/project.json @@ -1,5 +1,5 @@ { - "version": "6.0.1.0", + "version": "6.1.1.0", "authors": [ "Microsoft Corporation" ], "description": "Azure Storage SDK for ASP.NET 5 Preview", "dependencies": { diff --git a/Lib/ClassLibraryCommon/Blob/BlobEncryptionPolicy.cs b/Lib/ClassLibraryCommon/Blob/BlobEncryptionPolicy.cs index 6aaa8d9ac..f4145033e 100644 --- a/Lib/ClassLibraryCommon/Blob/BlobEncryptionPolicy.cs +++ b/Lib/ClassLibraryCommon/Blob/BlobEncryptionPolicy.cs @@ -246,5 +246,19 @@ internal ICryptoTransform CreateAndSetEncryptionContext(IDictionary storageAsyncResult = new StorageAsyncResult(callback, state); BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, this.BlobType, this.ServiceClient, false); + Action> prepareStorageAsyncResult = localStorageAsyncResult => + { + modifiedOptions.AssertPolicyIfRequired(); + + if (modifiedOptions.EncryptionPolicy != null) + { + ICryptoTransform transform = modifiedOptions.EncryptionPolicy.CreateAndSetEncryptionContext(this.Metadata, false /* noPadding */); + localStorageAsyncResult.Result = new BlobEncryptedWriteStream(this, accessCondition, modifiedOptions, operationContext, transform); + } + else + { + localStorageAsyncResult.Result = new BlobWriteStream(this, accessCondition, modifiedOptions, operationContext); + } + + localStorageAsyncResult.OnComplete(); + }; + if ((accessCondition != null) && accessCondition.IsConditional) { ICancellableAsyncResult result = this.BeginFetchAttributes( @@ -172,8 +189,7 @@ public ICancellableAsyncResult BeginOpenWrite(AccessCondition accessCondition, B return; } - storageAsyncResult.Result = new BlobWriteStream(this, accessCondition, modifiedOptions, operationContext); - storageAsyncResult.OnComplete(); + prepareStorageAsyncResult(storageAsyncResult); }, null /* state */); @@ -181,21 +197,7 @@ public ICancellableAsyncResult BeginOpenWrite(AccessCondition accessCondition, B } else { -#if !(WINDOWS_RT || ASPNET_K || PORTABLE) - modifiedOptions.AssertPolicyIfRequired(); - - if (modifiedOptions.EncryptionPolicy != null) - { - ICryptoTransform transform = modifiedOptions.EncryptionPolicy.CreateAndSetEncryptionContext(this.Metadata, false /* noPadding */); - storageAsyncResult.Result = new BlobEncryptedWriteStream(this, accessCondition, modifiedOptions, operationContext, transform); - } - else -#endif - { - storageAsyncResult.Result = new BlobWriteStream(this, accessCondition, modifiedOptions, operationContext); - } - - storageAsyncResult.OnComplete(); + prepareStorageAsyncResult(storageAsyncResult); } return storageAsyncResult; @@ -344,38 +346,68 @@ internal void UploadFromStreamHelper(Stream source, long? length, AccessConditio BlobRequestOptions modifiedOptions = BlobRequestOptions.ApplyDefaults(options, BlobType.BlockBlob, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); - bool lessThanSingleBlobThreshold = source.CanSeek - && (length ?? source.Length - source.Position) - <= modifiedOptions.SingleBlobUploadThresholdInBytes.Value; + bool lessThanSingleBlobThreshold = CloudBlockBlob.IsLessThanSingleBlobThreshold(source, length, modifiedOptions, false); modifiedOptions.AssertPolicyIfRequired(); - if (modifiedOptions.ParallelOperationThreadCount.Value == 1 && lessThanSingleBlobThreshold && modifiedOptions.EncryptionPolicy == null) + if (modifiedOptions.ParallelOperationThreadCount.Value == 1 && lessThanSingleBlobThreshold) { - string contentMD5 = null; - if (modifiedOptions.StoreBlobContentMD5.Value) + bool usingEncryption = modifiedOptions.EncryptionPolicy != null; + Stream sourceStream = source; + using (MemoryStream tempStream = !usingEncryption ? null : new MemoryStream()) { - using (ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions)) + // Encrypt if necessary + if (usingEncryption) { - StreamDescriptor streamCopyState = new StreamDescriptor(); - long startPosition = source.Position; - source.WriteToSync(Stream.Null, length, null /* maxLength */, true, true, tempExecutionState, streamCopyState); - source.Position = startPosition; - contentMD5 = streamCopyState.Md5; + modifiedOptions.AssertPolicyIfRequired(); + if (modifiedOptions.EncryptionPolicy.EncryptionMode != BlobEncryptionMode.FullBlob) + { + throw new InvalidOperationException(SR.InvalidEncryptionMode, null); + } + + ICryptoTransform transform = modifiedOptions.EncryptionPolicy.CreateAndSetEncryptionContext(this.Metadata, false /* noPadding */); + CryptoStream cryptoStream = new CryptoStream(tempStream, transform, CryptoStreamMode.Write); + using (ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(options)) + { + source.WriteToSync(cryptoStream, length, null, false, true, tempExecutionState, null); + cryptoStream.FlushFinalBlock(); + } + + // After the tempStream has been written to, we need to seek back to the beginning, so that it can be read from. + tempStream.Seek(0, SeekOrigin.Begin); + length = tempStream.Length; + sourceStream = tempStream; } - } - else - { - // Throw exception if we need to use Transactional MD5 but cannot store it - if (modifiedOptions.UseTransactionalMD5.Value) + + // Calculate MD5 if necessary + // Note that we cannot do this while we encrypt, it must be a separate step, because we want the MD5 of the encrypted data, + // not the unencrypted data. + string contentMD5 = null; + if (modifiedOptions.StoreBlobContentMD5.Value) { - throw new ArgumentException(SR.PutBlobNeedsStoreBlobContentMD5, "options"); + using (ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions)) + { + StreamDescriptor streamCopyState = new StreamDescriptor(); + long startPosition = sourceStream.Position; + sourceStream.WriteToSync(Stream.Null, length, null /* maxLength */, true, true, tempExecutionState, streamCopyState); + sourceStream.Position = startPosition; + contentMD5 = streamCopyState.Md5; + } + } + else + { + // Throw exception if we need to use Transactional MD5 but cannot store it + if (modifiedOptions.UseTransactionalMD5.Value) + { + throw new ArgumentException(SR.PutBlobNeedsStoreBlobContentMD5, "options"); + } } - } - Executor.ExecuteSync( - this.PutBlobImpl(source, length, contentMD5, accessCondition, modifiedOptions), - modifiedOptions.RetryPolicy, - operationContext); + // Execute the put blob. + Executor.ExecuteSync( + this.PutBlobImpl(sourceStream, length, contentMD5, accessCondition, modifiedOptions), + modifiedOptions.RetryPolicy, + operationContext); + } } else { @@ -483,76 +515,129 @@ internal ICancellableAsyncResult BeginUploadFromStreamHelper(Stream source, long ExecutionState tempExecutionState = CommonUtility.CreateTemporaryExecutionState(modifiedOptions); StorageAsyncResult storageAsyncResult = new StorageAsyncResult(callback, state); - bool lessThanSingleBlobThreshold = source.CanSeek && - (length ?? source.Length - source.Position) <= modifiedOptions.SingleBlobUploadThresholdInBytes.Value; + bool lessThanSingleBlobThreshold = CloudBlockBlob.IsLessThanSingleBlobThreshold(source, length, modifiedOptions, false); modifiedOptions.AssertPolicyIfRequired(); - if (modifiedOptions.ParallelOperationThreadCount.Value == 1 && lessThanSingleBlobThreshold && modifiedOptions.EncryptionPolicy == null) + if (modifiedOptions.ParallelOperationThreadCount.Value == 1 && lessThanSingleBlobThreshold) { - if (modifiedOptions.StoreBlobContentMD5.Value) - { - long startPosition = source.Position; - StreamDescriptor streamCopyState = new StreamDescriptor(); - source.WriteToAsync( - Stream.Null, - length, - null /* maxLength */, - true, - tempExecutionState, - streamCopyState, - completedState => + // Because we may or may not want to calculate the MD5, and we may or may not want to encrypt, rather than have four branching code + // paths, here we have an action that we will run, which continually gets added to, depending on which operations we need to do. + // The confusing part is that we have to build it from the bottom up. + + string md5 = null; + Stream sourceStream = source; + Action actionToRun = null; + + Action uploadAction = () => + { + if (md5 == null && modifiedOptions.UseTransactionalMD5.Value) { - storageAsyncResult.UpdateCompletedSynchronously(completedState.CompletedSynchronously); + throw new ArgumentException(SR.PutBlobNeedsStoreBlobContentMD5, "options"); + } - try - { - lock (storageAsyncResult.CancellationLockerObject) - { - storageAsyncResult.CancelDelegate = null; - if (completedState.ExceptionRef != null) - { - storageAsyncResult.OnComplete(completedState.ExceptionRef); - } - else - { - source.Position = startPosition; - this.UploadFromStreamHandler( - source, + this.UploadFromStreamHandler( + sourceStream, length, - streamCopyState.Md5, + md5, accessCondition, operationContext, modifiedOptions, storageAsyncResult); - } - } - } - catch (Exception e) + + }; + actionToRun = uploadAction; + + if (modifiedOptions.StoreBlobContentMD5.Value) + { + Action calculateMD5 = (continuation) => + { + long startPosition = sourceStream.Position; + StreamDescriptor streamCopyState = new StreamDescriptor(); + sourceStream.WriteToAsync( + Stream.Null, + length, + null /* maxLength */, + true, + tempExecutionState, + streamCopyState, + completedState => { - storageAsyncResult.OnComplete(e); - } - }); + ContinueAsyncOperation(storageAsyncResult, completedState, () => + { + if (completedState.ExceptionRef != null) + { + storageAsyncResult.OnComplete(completedState.ExceptionRef); + } + else + { + sourceStream.Position = startPosition; + md5 = streamCopyState.Md5; + continuation(); + } + }); + }); - // We do not need to do this inside a lock, as storageAsyncResult is - // not returned to the user yet. - storageAsyncResult.CancelDelegate = tempExecutionState.Cancel; + storageAsyncResult.CancelDelegate = tempExecutionState.Cancel; + if (storageAsyncResult.CancelRequested) + { + storageAsyncResult.Cancel(); + } + }; + Action oldActionToRun = actionToRun; + actionToRun = () => calculateMD5(oldActionToRun); } - else + + if (modifiedOptions.EncryptionPolicy != null) { - if (modifiedOptions.UseTransactionalMD5.Value) - { - throw new ArgumentException(SR.PutBlobNeedsStoreBlobContentMD5, "options"); - } + Action encryptStream = continuation => + { + SyncMemoryStream syncMemoryStream = new SyncMemoryStream(); + options.AssertPolicyIfRequired(); + + sourceStream = syncMemoryStream; + + if (modifiedOptions.EncryptionPolicy.EncryptionMode != BlobEncryptionMode.FullBlob) + { + throw new InvalidOperationException(SR.InvalidEncryptionMode, null); + } + + ICryptoTransform transform = options.EncryptionPolicy.CreateAndSetEncryptionContext(this.Metadata, false /* noPadding */); + CryptoStream cryptoStream = new CryptoStream(syncMemoryStream, transform, CryptoStreamMode.Write); + StreamDescriptor streamCopyState = new StreamDescriptor(); - this.UploadFromStreamHandler( - source, - length, - null /* contentMD5 */, - accessCondition, - operationContext, - modifiedOptions, - storageAsyncResult); + source.WriteToAsync(cryptoStream, length, null, false, tempExecutionState, streamCopyState, completedState => + { + ContinueAsyncOperation(storageAsyncResult, completedState, () => + { + if (completedState.ExceptionRef != null) + { + storageAsyncResult.OnComplete(completedState.ExceptionRef); + } + else + { + // Flush the CryptoStream in order to make sure that the last block of data is flushed. This call is a sync call + // but it is ok to have it because we're just writing to a memory stream. + cryptoStream.FlushFinalBlock(); + + // After the tempStream has been written to, we need to seek back to the beginning, so that it can be read from. + sourceStream.Seek(0, SeekOrigin.Begin); + length = syncMemoryStream.Length; + continuation(); + } + }); + }); + + storageAsyncResult.CancelDelegate = tempExecutionState.Cancel; + if (storageAsyncResult.CancelRequested) + { + storageAsyncResult.Cancel(); + } + }; + Action oldActionToRun = actionToRun; + actionToRun = () => encryptStream(oldActionToRun); } + + actionToRun(); } else { @@ -562,12 +647,7 @@ internal ICancellableAsyncResult BeginUploadFromStreamHelper(Stream source, long operationContext, ar => { - storageAsyncResult.UpdateCompletedSynchronously(ar.CompletedSynchronously); - - lock (storageAsyncResult.CancellationLockerObject) - { - storageAsyncResult.CancelDelegate = null; - try + ContinueAsyncOperation(storageAsyncResult, ar, () => { CloudBlobStream blobStream = this.EndOpenWrite(ar); storageAsyncResult.OperationState = blobStream; @@ -581,34 +661,25 @@ internal ICancellableAsyncResult BeginUploadFromStreamHelper(Stream source, long null /* streamCopyState */, completedState => { - storageAsyncResult.UpdateCompletedSynchronously(completedState.CompletedSynchronously); - if (completedState.ExceptionRef != null) + ContinueAsyncOperation(storageAsyncResult, completedState, () => { - storageAsyncResult.OnComplete(completedState.ExceptionRef); - } - else - { - try + if (completedState.ExceptionRef != null) { - lock (storageAsyncResult.CancellationLockerObject) - { - storageAsyncResult.CancelDelegate = null; - ICancellableAsyncResult commitResult = blobStream.BeginCommit( - CloudBlob.BlobOutputStreamCommitCallback, - storageAsyncResult); - - storageAsyncResult.CancelDelegate = commitResult.Cancel; - if (storageAsyncResult.CancelRequested) - { - storageAsyncResult.Cancel(); - } - } + storageAsyncResult.OnComplete(completedState.ExceptionRef); } - catch (Exception e) + else { - storageAsyncResult.OnComplete(e); + ICancellableAsyncResult commitResult = blobStream.BeginCommit( + CloudBlob.BlobOutputStreamCommitCallback, + storageAsyncResult); + + storageAsyncResult.CancelDelegate = commitResult.Cancel; + if (storageAsyncResult.CancelRequested) + { + storageAsyncResult.Cancel(); + } } - } + }); }); storageAsyncResult.CancelDelegate = tempExecutionState.Cancel; @@ -616,12 +687,7 @@ internal ICancellableAsyncResult BeginUploadFromStreamHelper(Stream source, long { storageAsyncResult.Cancel(); } - } - catch (Exception e) - { - storageAsyncResult.OnComplete(e); - } - } + }); }, null /* state */); @@ -2542,5 +2608,39 @@ internal RESTCommand CreateSnapshotImpl(IDictionary storageAsyncResult, IAsyncResult result, Action actionToTakeInTheLock) + { + storageAsyncResult.UpdateCompletedSynchronously(result.CompletedSynchronously); + try + { + lock (storageAsyncResult.CancellationLockerObject) + { + storageAsyncResult.CancelDelegate = null; + actionToTakeInTheLock(); + } + } + catch (Exception e) + { + storageAsyncResult.OnComplete(e); + } + } } } diff --git a/Lib/ClassLibraryCommon/Table/TableBatchOperation.cs b/Lib/ClassLibraryCommon/Table/TableBatchOperation.cs index 7081176fa..13b0fab21 100644 --- a/Lib/ClassLibraryCommon/Table/TableBatchOperation.cs +++ b/Lib/ClassLibraryCommon/Table/TableBatchOperation.cs @@ -40,15 +40,16 @@ public sealed partial class TableBatchOperation : IList /// /// The class of type for the entity to retrieve. /// A string containing the partition key of the entity to retrieve. - /// A string containing the row key of the entity to retrieve. + /// A string containing the row key of the entity to retrieve. + /// List of column names for projection. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Reviewed.")] - public void Retrieve(string partitionKey, string rowKey) where TElement : ITableEntity + public void Retrieve(string partitionKey, string rowKey, List selectedColumns = null) where TElement : ITableEntity { CommonUtility.AssertNotNull("partitionKey", partitionKey); CommonUtility.AssertNotNull("rowkey", rowKey); // Add the table operation. - this.Add(new TableOperation(null /* entity */, TableOperationType.Retrieve) { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowKey, RetrieveResolver = (pk, rk, ts, prop, etag) => EntityUtilities.ResolveEntityByType(pk, rk, ts, prop, etag) }); + this.Add(new TableOperation(null /* entity */, TableOperationType.Retrieve) { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowKey, SelectColumns = selectedColumns, RetrieveResolver = (pk, rk, ts, prop, etag) => EntityUtilities.ResolveEntityByType(pk, rk, ts, prop, etag) }); } /// @@ -58,13 +59,14 @@ public void Retrieve(string partitionKey, string rowKey) where TElemen /// A string containing the partition key of the entity to retrieve. /// A string containing the row key of the entity to retrieve. /// The implementation to project the entity to retrieve as a particular type in the result. - public void Retrieve(string partitionKey, string rowKey, EntityResolver resolver) + /// List of column names for projection. + public void Retrieve(string partitionKey, string rowKey, EntityResolver resolver, List selectedColumns = null) { CommonUtility.AssertNotNull("partitionKey", partitionKey); CommonUtility.AssertNotNull("rowkey", rowKey); // Add the table operation. - this.Add(new TableOperation(null /* entity */, TableOperationType.Retrieve) { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowKey, RetrieveResolver = (pk, rk, ts, prop, etag) => resolver(pk, rk, ts, prop, etag) }); + this.Add(new TableOperation(null /* entity */, TableOperationType.Retrieve) { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowKey, SelectColumns = selectedColumns, RetrieveResolver = (pk, rk, ts, prop, etag) => resolver(pk, rk, ts, prop, etag) }); } #endregion diff --git a/Lib/ClassLibraryCommon/Table/TableOperation.cs b/Lib/ClassLibraryCommon/Table/TableOperation.cs index d0266a313..5db324b60 100644 --- a/Lib/ClassLibraryCommon/Table/TableOperation.cs +++ b/Lib/ClassLibraryCommon/Table/TableOperation.cs @@ -20,6 +20,7 @@ namespace Microsoft.WindowsAzure.Storage.Table using Microsoft.WindowsAzure.Storage.Core; using Microsoft.WindowsAzure.Storage.Core.Executor; using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; using Microsoft.WindowsAzure.Storage.Table.Protocol; using System; using System.IO; @@ -201,6 +202,18 @@ private static RESTCommand RetrieveImpl(TableOperation operation, C requestOptions.ApplyToStorageCommand(retrieveCmd); TableResult result = new TableResult(); + if (operation.SelectColumns != null && operation.SelectColumns.Count > 0) + { + // If encryption policy is set, then add the encryption metadata column to Select columns in order to be able to decrypt properties. + if (requestOptions.EncryptionPolicy != null) + { + operation.SelectColumns.Add(Constants.EncryptionConstants.TableEncryptionKeyDetails); + operation.SelectColumns.Add(Constants.EncryptionConstants.TableEncryptionPropertyDetails); + } + + retrieveCmd.Builder = operation.GenerateQueryBuilder(); + } + retrieveCmd.CommandLocationMode = operation.isPrimaryOnlyRetrieve ? CommandLocationMode.PrimaryOnly : CommandLocationMode.PrimaryOrSecondary; retrieveCmd.RetrieveResponseStream = true; retrieveCmd.SignRequest = client.AuthenticationHandler.SignRequest; diff --git a/Lib/ClassLibraryCommon/Table/TableQuery.cs b/Lib/ClassLibraryCommon/Table/TableQuery.cs index b4f5a502c..ceccc9987 100644 --- a/Lib/ClassLibraryCommon/Table/TableQuery.cs +++ b/Lib/ClassLibraryCommon/Table/TableQuery.cs @@ -538,7 +538,7 @@ private static RESTCommand> QueryImpl 0) { query.SelectColumns.Add(Constants.EncryptionConstants.TableEncryptionKeyDetails); query.SelectColumns.Add(Constants.EncryptionConstants.TableEncryptionPropertyDetails); diff --git a/Lib/Common/Shared/Protocol/Constants.cs b/Lib/Common/Shared/Protocol/Constants.cs index c39f826fc..d193a424e 100644 --- a/Lib/Common/Shared/Protocol/Constants.cs +++ b/Lib/Common/Shared/Protocol/Constants.cs @@ -786,9 +786,9 @@ static HeaderConstants() /// Specifies the value to use for UserAgent header. /// #if ASPNET_K || PORTABLE - public const string UserAgentProductVersion = "6.0.1-preview"; + public const string UserAgentProductVersion = "6.1.1-preview"; #else - public const string UserAgentProductVersion = "6.0.0"; + public const string UserAgentProductVersion = "6.1.0"; #endif /// diff --git a/Lib/Common/Table/TableOperation.Common.cs b/Lib/Common/Table/TableOperation.Common.cs index b8f9c8ca1..b998bda70 100644 --- a/Lib/Common/Table/TableOperation.Common.cs +++ b/Lib/Common/Table/TableOperation.Common.cs @@ -24,6 +24,7 @@ namespace Microsoft.WindowsAzure.Storage.Table using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; + using System.Text; /// /// Represents a single table operation. @@ -112,6 +113,11 @@ internal Func internal bool EchoContent { get; set; } + /// + /// List of columns to project with for the retrieve operation. + /// + internal List SelectColumns { get; set; } + /// /// Creates a new table operation that deletes the given entity /// from a table. @@ -240,13 +246,14 @@ public static TableOperation Replace(ITableEntity entity) /// /// The class of type for the entity to retrieve. /// A string containing the partition key of the entity to retrieve. - /// A string containing the row key of the entity to retrieve. + /// A string containing the row key of the entity to retrieve. + /// List of column names for projection. /// The object. [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Reviewed")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "rowkey", Justification = "Reviewed : towkey is acceptable.")] - public static TableOperation Retrieve(string partitionKey, string rowkey) + public static TableOperation Retrieve(string partitionKey, string rowkey, List selectColumns = null) where TElement : ITableEntity { CommonUtility.AssertNotNull("partitionKey", partitionKey); @@ -257,6 +264,7 @@ public static TableOperation Retrieve(string partitionKey, string rowk { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowkey, + SelectColumns = selectColumns, RetrieveResolver = (pk, rk, ts, prop, etag) => EntityUtilities.ResolveEntityByType( pk, @@ -276,15 +284,16 @@ public static TableOperation Retrieve(string partitionKey, string rowk /// A string containing the partition key of the entity to retrieve. /// A string containing the row key of the entity to retrieve. /// The implementation to project the entity to retrieve as a particular type in the result. + /// List of column names for projection. /// The object. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "rowkey", Justification = "Reviewed : rowkey is acceptable.")] - public static TableOperation Retrieve(string partitionKey, string rowkey, EntityResolver resolver) + public static TableOperation Retrieve(string partitionKey, string rowkey, EntityResolver resolver, List selectedColumns = null) { CommonUtility.AssertNotNull("partitionKey", partitionKey); CommonUtility.AssertNotNull("rowkey", rowkey); // Create and return the table operation. - return new TableOperation(null /* entity */, TableOperationType.Retrieve) { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowkey, RetrieveResolver = (pk, rk, ts, prop, etag) => resolver(pk, rk, ts, prop, etag) }; + return new TableOperation(null /* entity */, TableOperationType.Retrieve) { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowkey, RetrieveResolver = (pk, rk, ts, prop, etag) => resolver(pk, rk, ts, prop, etag), SelectColumns = selectedColumns }; } /// @@ -293,15 +302,16 @@ public static TableOperation Retrieve(string partitionKey, string rowke /// /// A string containing the partition key of the entity to be retrieved. /// A string containing the row key of the entity to be retrieved. + /// List of column names for projection. /// The object. [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "rowkey", Justification = "Reviewed : rowkey is allowed.")] - public static TableOperation Retrieve(string partitionKey, string rowkey) + public static TableOperation Retrieve(string partitionKey, string rowkey, List selectedColumns = null) { CommonUtility.AssertNotNull("partitionKey", partitionKey); CommonUtility.AssertNotNull("rowkey", rowkey); // Create and return the table operation. - return new TableOperation(null /* entity */, TableOperationType.Retrieve) { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowkey }; + return new TableOperation(null /* entity */, TableOperationType.Retrieve) { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowkey, SelectColumns = selectedColumns }; } private static object DynamicEntityResolver(string partitionKey, string rowKey, DateTimeOffset timestamp, IDictionary properties, string etag) @@ -357,5 +367,63 @@ internal Uri GenerateRequestURI(Uri uri, string tableName) return NavigationHelper.AppendPathToSingleUri(uri, string.Format(CultureInfo.InvariantCulture, "{0}({1})", tableName, identity)); } } + + internal UriQueryBuilder GenerateQueryBuilder() + { + UriQueryBuilder builder = new UriQueryBuilder(); + + // select + if (this.SelectColumns != null && this.SelectColumns.Count > 0) + { + StringBuilder colBuilder = new StringBuilder(); + bool foundRk = false; + bool foundPk = false; + bool foundTs = false; + + for (int m = 0; m < this.SelectColumns.Count; m++) + { + if (this.SelectColumns[m] == TableConstants.PartitionKey) + { + foundPk = true; + } + else if (this.SelectColumns[m] == TableConstants.RowKey) + { + foundRk = true; + } + else if (this.SelectColumns[m] == TableConstants.Timestamp) + { + foundTs = true; + } + + colBuilder.Append(this.SelectColumns[m]); + if (m < this.SelectColumns.Count - 1) + { + colBuilder.Append(","); + } + } + + if (!foundPk) + { + colBuilder.Append(","); + colBuilder.Append(TableConstants.PartitionKey); + } + + if (!foundRk) + { + colBuilder.Append(","); + colBuilder.Append(TableConstants.RowKey); + } + + if (!foundTs) + { + colBuilder.Append(","); + colBuilder.Append(TableConstants.Timestamp); + } + + builder.Add(TableConstants.Select, colBuilder.ToString()); + } + + return builder; + } } } diff --git a/Lib/Common/Table/TableQuery.Common.cs b/Lib/Common/Table/TableQuery.Common.cs index 260f9c0fb..f2b15e7aa 100644 --- a/Lib/Common/Table/TableQuery.Common.cs +++ b/Lib/Common/Table/TableQuery.Common.cs @@ -172,8 +172,9 @@ private static string GenerateFilterCondition(string propertyName, string operat } else if (edmType == EdmType.Double) { - bool decimalExists = givenValue.Contains("."); - valueOperand = decimalExists ? givenValue : string.Format(CultureInfo.InvariantCulture, "{0}.0", givenValue); + int parsedInt; + bool isInteger = int.TryParse(givenValue, out parsedInt); + valueOperand = isInteger ? string.Format(CultureInfo.InvariantCulture, "{0}.0", givenValue) : givenValue; } else if (edmType == EdmType.Int64) { diff --git a/Lib/Portable/Properties/AssemblyInfo.cs b/Lib/Portable/Properties/AssemblyInfo.cs index 113fe16fa..7394bb30c 100644 --- a/Lib/Portable/Properties/AssemblyInfo.cs +++ b/Lib/Portable/Properties/AssemblyInfo.cs @@ -26,6 +26,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] -[assembly: AssemblyInformationalVersion("6.0.1.0-preview")] +[assembly: AssemblyVersion("6.1.1.0")] +[assembly: AssemblyFileVersion("6.1.1.0")] +[assembly: AssemblyInformationalVersion("6.1.1.0-preview")] diff --git a/Lib/WindowsDesktop/GlobalSuppressions.cs b/Lib/WindowsDesktop/GlobalSuppressions.cs index ede31cc4a..be0239812 100644 --- a/Lib/WindowsDesktop/GlobalSuppressions.cs +++ b/Lib/WindowsDesktop/GlobalSuppressions.cs @@ -28,6 +28,13 @@ [assembly: SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableQuery`1.#GenerateFilterConditionForInt(System.String,System.String,System.Int32)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableQuery`1.#GenerateFilterConditionForDouble(System.String,System.String,System.Double)", Justification = "Backward compatibility")] +// CA1002 +[assembly: SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableBatchOperation.#Retrieve`1(System.String,System.String,System.Collections.Generic.List`1)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableBatchOperation.#Retrieve`1(System.String,System.String,Microsoft.WindowsAzure.Storage.Table.EntityResolver`1,System.Collections.Generic.List`1)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableOperation.#Retrieve`1(System.String,System.String,System.Collections.Generic.List`1)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableOperation.#Retrieve`1(System.String,System.String,Microsoft.WindowsAzure.Storage.Table.EntityResolver`1,System.Collections.Generic.List`1)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableOperation.#Retrieve(System.String,System.String,System.Collections.Generic.List`1)", Justification = "Backward compatibility")] + // CA 1008 [assembly: SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Scope = "type", Target = "Microsoft.WindowsAzure.Storage.SharedAccessProtocol")] @@ -326,6 +333,11 @@ [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#AppendText(System.String,System.Text.Encoding,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.#CreateOrReplace(Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] [assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.File.CloudFileShare.#GetStats(Microsoft.WindowsAzure.Storage.File.FileRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableBatchOperation.#Retrieve`1(System.String,System.String,System.Collections.Generic.List`1)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableOperation.#Retrieve`1(System.String,System.String,System.Collections.Generic.List`1)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableOperation.#Retrieve`1(System.String,System.String,Microsoft.WindowsAzure.Storage.Table.EntityResolver`1,System.Collections.Generic.List`1)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableOperation.#Retrieve(System.String,System.String,System.Collections.Generic.List`1)", Justification = "Backward compatibility")] +[assembly: SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableBatchOperation.#Retrieve`1(System.String,System.String,Microsoft.WindowsAzure.Storage.Table.EntityResolver`1,System.Collections.Generic.List`1)", Justification = "Backward compatibility")] // CA1031 [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#BeginOpenWrite(Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Needed to ensure exceptions are not thrown on threadpool thread.")] @@ -405,6 +417,7 @@ [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.File.CloudFile.#BeginDownloadToFile(System.String,System.IO.FileMode,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.File.FileRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#DownloadToFile(System.String,System.IO.FileMode,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#BeginDownloadToFile(System.String,System.IO.FileMode,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#StandardEndAsyncWithAction(Microsoft.WindowsAzure.Storage.Core.Util.StorageAsyncResult`1,System.IAsyncResult,System.Action)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob.#BeginDownloadToFile(System.String,System.IO.FileMode,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudPageBlob.#DownloadToFile(System.String,System.IO.FileMode,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#DownloadToFile(System.String,System.IO.FileMode,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed.")] @@ -422,6 +435,7 @@ [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.BlobDecryptStream.#WriteStreamCallback(System.IAsyncResult)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.LengthLimitingStream.#WriteStreamCallback(System.IAsyncResult)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Executor.ExecutionState`1.#CheckDisposeAction()", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#ContinueAsyncOperation(Microsoft.WindowsAzure.Storage.Core.Util.StorageAsyncResult`1,System.IAsyncResult,System.Action)", Justification = "Reviewed.")] // CA1040 [assembly: SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Scope = "type", Target = "Microsoft.WindowsAzure.Storage.IContinuationToken", Justification = "Reviewed : Specifies a common base type a continuation token can be cast to")] @@ -584,6 +598,7 @@ [assembly: SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#EndStartCopyFromBlob(System.IAsyncResult)", Justification = "APM end methods should not be static")] [assembly: SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlob.#BlobType", Justification = "Reviewed")] [assembly: SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.TableEncryptionPolicy.#DecryptEntity(System.Collections.Generic.IDictionary`2,System.Collections.Generic.HashSet`1,System.String,System.String,System.Byte[],Microsoft.WindowsAzure.Storage.EncryptionData)", Justification = "Reviewed")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptionPolicy.#GetEncryptedLength(System.Int64,System.Boolean)", Justification = "Reviewed")] // CA1823 [assembly: SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Table.Queryable.ResourceBinder+PropertyInfoEqualityComparer.#Instance", Justification = "Reviewed")] @@ -733,8 +748,15 @@ [assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequestOptions", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions.#AssertNoEncryptionPolicyOrStrictMode()", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequireEncryption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobEncryptionPolicy.#DecryptBlob(System.IO.Stream,System.Collections.Generic.IDictionary`2,System.Security.Cryptography.ICryptoTransform&,System.Nullable`1,System.Byte[],System.Boolean)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "RequireEncryption", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions.#AssertPolicyIfRequired()", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "UploadFromStream", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#UploadFromStreamHelper(System.IO.Stream,System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "FullBlob", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#UploadFromStreamHelper(System.IO.Stream,System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "BlobEncryptionMode", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#UploadFromStreamHelper(System.IO.Stream,System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "UploadFromStream", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#BeginUploadFromStreamHelper(System.IO.Stream,System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "FullBlob", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#BeginUploadFromStreamHelper(System.IO.Stream,System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "BlobEncryptionMode", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#BeginUploadFromStreamHelper(System.IO.Stream,System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] // CA2208 +[assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.#BeginUploadFromStreamHelper(System.IO.Stream,System.Nullable`1,Microsoft.WindowsAzure.Storage.AccessCondition,Microsoft.WindowsAzure.Storage.Blob.BlobRequestOptions,Microsoft.WindowsAzure.Storage.OperationContext,System.AsyncCallback,System.Object)", Justification = "Reviewed.")] [assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.WindowsAzure.Storage.Core.Util.AsyncStreamCopier`1.#EndOperation(System.IAsyncResult)", Justification = "Reviewed")] // CA2214 diff --git a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs index 1cb7c136c..8ddee6616 100644 --- a/Lib/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Lib/WindowsDesktop/Properties/AssemblyInfo.cs @@ -34,8 +34,8 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] #if SIGN [assembly: InternalsVisibleTo( diff --git a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec index b7ba52029..2c811ff89 100644 --- a/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec +++ b/Lib/WindowsDesktop/WindowsAzure.Storage.nuspec @@ -2,7 +2,7 @@ WindowsAzure.Storage - 6.0.0 + 6.1.0 Windows Azure Storage Microsoft Microsoft diff --git a/Lib/WindowsPhone/Properties/AssemblyInfo.cs b/Lib/WindowsPhone/Properties/AssemblyInfo.cs index 33010bf98..d2aae5b41 100644 --- a/Lib/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhone/Properties/AssemblyInfo.cs @@ -32,8 +32,8 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] #if SIGN diff --git a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs index 09179d06a..1204b4f80 100644 --- a/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs +++ b/Lib/WindowsPhoneRT/Properties/AssemblyInfo.cs @@ -24,8 +24,8 @@ // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] [assembly: ComVisible(false)] diff --git a/Lib/WindowsRuntime/Blob/CloudBlobClient.cs b/Lib/WindowsRuntime/Blob/CloudBlobClient.cs index d7cced852..2e650b928 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlobClient.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlobClient.cs @@ -89,14 +89,13 @@ internal HttpClientHandler AuthenticationHandler } } -#if !PORTABLE /// /// Returns a result segment containing a collection of containers. /// /// A continuation token returned by a previous listing operation. /// A result segment of containers. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task ListContainersSegmentedAsync(BlobContinuationToken currentToken) #else public IAsyncOperation ListContainersSegmentedAsync(BlobContinuationToken currentToken) @@ -111,7 +110,7 @@ public IAsyncOperation ListContainersSegmentedAsync(Blob /// A continuation token returned by a previous listing operation. /// A result segment of containers. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task ListContainersSegmentedAsync(string prefix, BlobContinuationToken currentToken) #else public IAsyncOperation ListContainersSegmentedAsync(string prefix, BlobContinuationToken currentToken) @@ -133,7 +132,7 @@ public IAsyncOperation ListContainersSegmentedAsync(stri /// An object that represents the context for the current operation. /// A result segment of containers. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task ListContainersSegmentedAsync(string prefix, ContainerListingDetails detailsIncluded, int? maxResults, BlobContinuationToken currentToken, BlobRequestOptions options, OperationContext operationContext) { return this.ListContainersSegmentedAsync(prefix, detailsIncluded, maxResults, currentToken, options, operationContext, CancellationToken.None); @@ -155,7 +154,7 @@ public IAsyncOperation ListContainersSegmentedAsync(stri } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Returns a result segment containing a collection of containers /// whose names begin with the specified prefix. @@ -184,7 +183,6 @@ public Task ListContainersSegmentedAsync(string prefix, return new ContainerResultSegment(resultSegment.Results, (BlobContinuationToken)resultSegment.ContinuationToken); }, cancellationToken); } -#endif #endif /// @@ -482,13 +480,12 @@ private RESTCommand GetBlobReferenceImpl(StorageUri blobUri, AccessC #region Analytics -#if !PORTABLE /// /// Gets the properties of the blob service. /// /// The blob service properties. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetServicePropertiesAsync() #else public IAsyncOperation GetServicePropertiesAsync() @@ -504,7 +501,7 @@ public IAsyncOperation GetServicePropertiesAsync() /// An object that represents the context for the current operation. /// The blob service properties. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetServicePropertiesAsync(BlobRequestOptions options, OperationContext operationContext) { return this.GetServicePropertiesAsync(options, operationContext, CancellationToken.None); @@ -524,7 +521,7 @@ public IAsyncOperation GetServicePropertiesAsync(BlobRequestO } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Gets the properties of the blob service. /// @@ -575,7 +572,7 @@ private RESTCommand GetServicePropertiesImpl(BlobRequestOptio /// The blob service properties. /// The properties of the blob service. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task SetServicePropertiesAsync(ServiceProperties properties) #else public IAsyncAction SetServicePropertiesAsync(ServiceProperties properties) @@ -592,7 +589,7 @@ public IAsyncAction SetServicePropertiesAsync(ServiceProperties properties) /// An object that represents the context for the current operation. /// The properties of the blob service. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task SetServicePropertiesAsync(ServiceProperties properties, BlobRequestOptions requestOptions, OperationContext operationContext) { return this.SetServicePropertiesAsync(properties, requestOptions, operationContext, CancellationToken.None); @@ -610,7 +607,7 @@ public IAsyncAction SetServicePropertiesAsync(ServiceProperties properties, Blob } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Gets the properties of the blob service. /// @@ -664,7 +661,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert /// /// The blob service stats. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetServiceStatsAsync() #else public IAsyncOperation GetServiceStatsAsync() @@ -680,7 +677,7 @@ public IAsyncOperation GetServiceStatsAsync() /// An object that represents the context for the current operation. /// The blob service stats. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetServiceStatsAsync(BlobRequestOptions options, OperationContext operationContext) { return this.GetServiceStatsAsync(options, operationContext, CancellationToken.None); @@ -700,7 +697,7 @@ public IAsyncOperation GetServiceStatsAsync(BlobRequestOptions opt } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Gets service stats for the Blob service. /// @@ -736,7 +733,6 @@ private RESTCommand GetServiceStatsImpl(BlobRequestOptions request retCmd.PostProcessResponse = (cmd, resp, ctx) => Task.Factory.StartNew(() => BlobHttpResponseParsers.ReadServiceStats(cmd.ResponseStream)); return retCmd; } -#endif #endregion } } diff --git a/Lib/WindowsRuntime/Blob/CloudBlobContainer.cs b/Lib/WindowsRuntime/Blob/CloudBlobContainer.cs index 66048d98c..fe5bc7c35 100644 --- a/Lib/WindowsRuntime/Blob/CloudBlobContainer.cs +++ b/Lib/WindowsRuntime/Blob/CloudBlobContainer.cs @@ -42,11 +42,10 @@ namespace Microsoft.WindowsAzure.Storage.Blob /// Containers hold directories, which are encapsulated as objects, and directories hold block blobs and page blobs. Directories can also contain sub-directories. public sealed partial class CloudBlobContainer { -#if !PORTABLE /// /// Creates the container. /// -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync() @@ -64,7 +63,7 @@ public IAsyncAction CreateAsync() /// /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync(BlobRequestOptions options, OperationContext operationContext) @@ -83,7 +82,7 @@ public IAsyncAction CreateAsync(BlobRequestOptions options, OperationContext ope /// An object that specifies whether data in the container may be accessed publicly and what level of access is to be allowed. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync(BlobContainerPublicAccessType accessType, BlobRequestOptions options, OperationContext operationContext) @@ -104,7 +103,7 @@ public IAsyncAction CreateAsync(BlobContainerPublicAccessType accessType, BlobRe } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Creates the container and specifies the level of access to the container's data. /// @@ -130,7 +129,7 @@ public Task CreateAsync(BlobContainerPublicAccessType accessType, BlobRequestOpt /// /// true if the container did not already exist and was created; otherwise, false. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task CreateIfNotExistsAsync() #else public IAsyncOperation CreateIfNotExistsAsync() @@ -146,7 +145,7 @@ public IAsyncOperation CreateIfNotExistsAsync() /// An object that represents the context for the current operation. /// true if the container did not already exist and was created; otherwise false. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task CreateIfNotExistsAsync(BlobRequestOptions options, OperationContext operationContext) #else public IAsyncOperation CreateIfNotExistsAsync(BlobRequestOptions options, OperationContext operationContext) @@ -163,7 +162,7 @@ public IAsyncOperation CreateIfNotExistsAsync(BlobRequestOptions options, /// An object that represents the context for the current operation. /// true if the container did not already exist and was created; otherwise false. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task CreateIfNotExistsAsync(BlobContainerPublicAccessType accessType, BlobRequestOptions options, OperationContext operationContext) { return this.CreateIfNotExistsAsync(accessType, options, operationContext, CancellationToken.None); @@ -212,7 +211,7 @@ public IAsyncOperation CreateIfNotExistsAsync(BlobContainerPublicAccessTyp } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Creates the container if it does not already exist and specifies the level of access to the container's data. /// @@ -268,7 +267,7 @@ public Task CreateIfNotExistsAsync(BlobContainerPublicAccessType accessTyp /// /// Deletes the container. /// -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync() @@ -287,7 +286,7 @@ public IAsyncAction DeleteAsync() /// An object that represents the access conditions for the container. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) @@ -308,7 +307,7 @@ public IAsyncAction DeleteAsync(AccessCondition accessCondition, BlobRequestOpti } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Deletes the container. /// @@ -334,7 +333,7 @@ public Task DeleteAsync(AccessCondition accessCondition, BlobRequestOptions opti /// /// true if the container already existed and was deleted; otherwise, false. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task DeleteIfExistsAsync() #else public IAsyncOperation DeleteIfExistsAsync() @@ -351,7 +350,7 @@ public IAsyncOperation DeleteIfExistsAsync() /// An object that represents the context for the current operation. /// true if the container already existed and was deleted; otherwise, false. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task DeleteIfExistsAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { return this.DeleteIfExistsAsync(accessCondition, options, operationContext, CancellationToken.None); @@ -400,7 +399,7 @@ public IAsyncOperation DeleteIfExistsAsync(AccessCondition accessCondition } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Deletes the container if it already exists. /// @@ -451,7 +450,6 @@ public Task DeleteIfExistsAsync(AccessCondition accessCondition, BlobReque } }, cancellationToken); } -#endif #endif /// @@ -616,7 +614,7 @@ public Task ListBlobsSegmentedAsync(string prefix, bool useFl /// Sets permissions for the container. /// /// The permissions to apply to the container. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetPermissionsAsync(BlobContainerPermissions permissions) @@ -636,7 +634,7 @@ public IAsyncAction SetPermissionsAsync(BlobContainerPermissions permissions) /// An object that represents the access conditions for the container. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetPermissionsAsync(BlobContainerPermissions permissions, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) @@ -657,7 +655,7 @@ public IAsyncAction SetPermissionsAsync(BlobContainerPermissions permissions, Ac } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Sets permissions for the container. /// @@ -684,7 +682,7 @@ public Task SetPermissionsAsync(BlobContainerPermissions permissions, AccessCond /// /// The container's permissions. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetPermissionsAsync() #else public IAsyncOperation GetPermissionsAsync() @@ -701,7 +699,7 @@ public IAsyncOperation GetPermissionsAsync() /// An object that represents the context for the current operation. /// The container's permissions. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetPermissionsAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { return this.GetPermissionsAsync(accessCondition, options, operationContext, CancellationToken.None); @@ -718,7 +716,7 @@ public IAsyncOperation GetPermissionsAsync(AccessCondi } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Gets the permissions settings for the container. /// @@ -876,11 +874,10 @@ public Task FetchAttributesAsync(AccessCondition accessCondition, BlobRequestOpt } #endif -#if !PORTABLE /// /// Sets the container's user-defined metadata. /// -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetMetadataAsync() @@ -901,7 +898,7 @@ public IAsyncAction SetMetadataAsync() /// An object that represents the context for the current operation. /// An that represents an asynchronous action. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task SetMetadataAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { return this.SetMetadataAsync(accessCondition, options, operationContext, CancellationToken.None); @@ -918,7 +915,7 @@ public IAsyncAction SetMetadataAsync(AccessCondition accessCondition, BlobReques } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Sets the container's user-defined metadata. /// @@ -948,7 +945,7 @@ public Task SetMetadataAsync(AccessCondition accessCondition, BlobRequestOptions /// A string representing the proposed lease ID for the new lease, or null if no lease ID is proposed. /// The ID of the acquired lease. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task AcquireLeaseAsync(TimeSpan? leaseTime, string proposedLeaseId) #else public IAsyncOperation AcquireLeaseAsync(TimeSpan? leaseTime, string proposedLeaseId) @@ -969,7 +966,7 @@ public IAsyncOperation AcquireLeaseAsync(TimeSpan? leaseTime, string pro /// An object that represents the context for the current operation. /// The ID of the acquired lease. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task AcquireLeaseAsync(TimeSpan? leaseTime, string proposedLeaseId, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { return this.AcquireLeaseAsync(leaseTime, proposedLeaseId, accessCondition, options, operationContext, CancellationToken.None); @@ -986,7 +983,7 @@ public IAsyncOperation AcquireLeaseAsync(TimeSpan? leaseTime, string pro } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Acquires a lease on this container. /// @@ -1015,7 +1012,7 @@ public Task AcquireLeaseAsync(TimeSpan? leaseTime, string proposedLeaseI /// Renews a lease on this container. /// /// An object that represents the access conditions for the container, including a required lease ID. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task RenewLeaseAsync(AccessCondition accessCondition) @@ -1034,7 +1031,7 @@ public IAsyncAction RenewLeaseAsync(AccessCondition accessCondition) /// An object that represents the access conditions for the container, including a required lease ID. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task RenewLeaseAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) @@ -1055,7 +1052,7 @@ public IAsyncAction RenewLeaseAsync(AccessCondition accessCondition, BlobRequest } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Renews a lease on this container. /// @@ -1083,7 +1080,7 @@ public Task RenewLeaseAsync(AccessCondition accessCondition, BlobRequestOptions /// An object that represents the access conditions for the container, including a required lease ID. /// The new lease ID. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task ChangeLeaseAsync(string proposedLeaseId, AccessCondition accessCondition) #else public IAsyncOperation ChangeLeaseAsync(string proposedLeaseId, AccessCondition accessCondition) @@ -1101,7 +1098,7 @@ public IAsyncOperation ChangeLeaseAsync(string proposedLeaseId, AccessCo /// An object that represents the context for the current operation. /// The new lease ID. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task ChangeLeaseAsync(string proposedLeaseId, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { return this.ChangeLeaseAsync(proposedLeaseId, accessCondition, options, operationContext, CancellationToken.None); @@ -1118,7 +1115,7 @@ public IAsyncOperation ChangeLeaseAsync(string proposedLeaseId, AccessCo } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Changes the lease ID on this container. /// @@ -1144,7 +1141,7 @@ public Task ChangeLeaseAsync(string proposedLeaseId, AccessCondition acc /// Releases the lease on this container. /// /// An object that represents the access conditions for the container, including a required lease ID. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task ReleaseLeaseAsync(AccessCondition accessCondition) @@ -1163,7 +1160,7 @@ public IAsyncAction ReleaseLeaseAsync(AccessCondition accessCondition) /// An object that represents the access conditions for the container, including a required lease ID. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. This object is used to track requests, and to provide additional runtime information about the operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task ReleaseLeaseAsync(AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) @@ -1184,7 +1181,7 @@ public IAsyncAction ReleaseLeaseAsync(AccessCondition accessCondition, BlobReque } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Releases the lease on this container. /// @@ -1213,7 +1210,7 @@ public Task ReleaseLeaseAsync(AccessCondition accessCondition, BlobRequestOption /// or zero for infinite leases. /// A representing the amount of time before the lease ends, to the second. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task BreakLeaseAsync(TimeSpan? breakPeriod) #else public IAsyncOperation BreakLeaseAsync(TimeSpan? breakPeriod) @@ -1233,7 +1230,7 @@ public IAsyncOperation BreakLeaseAsync(TimeSpan? breakPeriod) /// An object that represents the context for the current operation. This object is used to track requests, and to provide additional runtime information about the operation. /// A representing the amount of time before the lease ends, to the second. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task BreakLeaseAsync(TimeSpan? breakPeriod, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) { return this.BreakLeaseAsync(breakPeriod, accessCondition, options, operationContext, CancellationToken.None); @@ -1250,7 +1247,7 @@ public IAsyncOperation BreakLeaseAsync(TimeSpan? breakPeriod, AccessCo } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Breaks the current lease on this container. /// @@ -1492,7 +1489,6 @@ private RESTCommand DeleteContainerImpl(AccessCondition accessConditio return putCmd; } -#endif /// /// Implementation for the FetchAttributes method. @@ -1580,6 +1576,7 @@ private RESTCommand SetMetadataImpl(AccessCondition accessCondition, B return putCmd; } +#if !PORTABLE /// /// Implementation for the SetPermissions method. /// @@ -1649,6 +1646,7 @@ private RESTCommand GetPermissionsImpl(AccessCondition return getCmd; } +#endif /// /// Selects the protocol response. diff --git a/Lib/WindowsRuntime/Blob/CloudPageBlob.cs b/Lib/WindowsRuntime/Blob/CloudPageBlob.cs index 091380d7d..0124fa853 100644 --- a/Lib/WindowsRuntime/Blob/CloudPageBlob.cs +++ b/Lib/WindowsRuntime/Blob/CloudPageBlob.cs @@ -565,12 +565,11 @@ public Task CreateAsync(long size, AccessCondition accessCondition, BlobRequestO } #endif -#if !PORTABLE /// /// Resizes the page blob to the specified size. /// /// The maximum size of the page blob, in bytes. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task ResizeAsync(long size) @@ -590,7 +589,7 @@ public IAsyncAction ResizeAsync(long size) /// An object that represents the access conditions for the blob. If null, no condition is used. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task ResizeAsync(long size, AccessCondition accessCondition, BlobRequestOptions options, OperationContext operationContext) @@ -611,7 +610,7 @@ public IAsyncAction ResizeAsync(long size, AccessCondition accessCondition, Blob } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Resizes the page blob to the specified size. /// @@ -631,7 +630,6 @@ public Task ResizeAsync(long size, AccessCondition accessCondition, BlobRequestO operationContext, cancellationToken), cancellationToken); } -#endif #endif /// diff --git a/Lib/WindowsRuntime/File/CloudFile.cs b/Lib/WindowsRuntime/File/CloudFile.cs index 9548ea26b..95ecc60fe 100644 --- a/Lib/WindowsRuntime/File/CloudFile.cs +++ b/Lib/WindowsRuntime/File/CloudFile.cs @@ -2486,6 +2486,7 @@ private void UpdateAfterFetchAttributes(HttpResponseMessage response, bool ignor this.attributes.Properties = properties; this.attributes.Metadata = FileHttpResponseParsers.GetMetadata(response); + this.attributes.CopyState = FileHttpResponseParsers.GetCopyAttributes(response); } /// diff --git a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs index f87f1ca0e..0fbe7495c 100644 --- a/Lib/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Lib/WindowsRuntime/Properties/AssemblyInfo.cs @@ -25,8 +25,8 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] [assembly: ComVisible(false)] #if SIGN diff --git a/Lib/WindowsRuntime/Queue/CloudQueue.cs b/Lib/WindowsRuntime/Queue/CloudQueue.cs index 6bc1bd936..6eead5749 100644 --- a/Lib/WindowsRuntime/Queue/CloudQueue.cs +++ b/Lib/WindowsRuntime/Queue/CloudQueue.cs @@ -41,11 +41,10 @@ namespace Microsoft.WindowsAzure.Storage.Queue /// public sealed partial class CloudQueue { -#if !PORTABLE /// /// Creates the queue. /// -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync() @@ -63,7 +62,7 @@ public IAsyncAction CreateAsync() /// /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task CreateAsync(QueueRequestOptions options, OperationContext operationContext) @@ -86,7 +85,7 @@ public IAsyncAction CreateAsync(QueueRequestOptions options, OperationContext op } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Creates the queue. /// @@ -113,7 +112,7 @@ public Task CreateAsync(QueueRequestOptions options, OperationContext operationC /// /// true if the queue did not already exist and was created; otherwise, false. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task CreateIfNotExistsAsync() #else public IAsyncOperation CreateIfNotExistsAsync() @@ -129,7 +128,7 @@ public IAsyncOperation CreateIfNotExistsAsync() /// An object that represents the context for the current operation. /// true if the queue did not already exist and was created; otherwise false. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task CreateIfNotExistsAsync(QueueRequestOptions options, OperationContext operationContext) { return this.CreateIfNotExistsAsync(options, operationContext, CancellationToken.None); @@ -151,7 +150,7 @@ public IAsyncOperation CreateIfNotExistsAsync(QueueRequestOptions options, QueueRequestOptions modifiedOptions = QueueRequestOptions.ApplyDefaults(options, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); -#if ASPNET_K +#if ASPNET_K || PORTABLE return Task.Run(async () => { bool exists = await this.ExistsAsync(true, modifiedOptions, operationContext, cancellationToken); @@ -168,7 +167,7 @@ public IAsyncOperation CreateIfNotExistsAsync(QueueRequestOptions options, try { -#if ASPNET_K +#if ASPNET_K || PORTABLE await this.CreateAsync(modifiedOptions, operationContext, cancellationToken); #else await this.CreateAsync(modifiedOptions, operationContext).AsTask(token); @@ -200,7 +199,7 @@ public IAsyncOperation CreateIfNotExistsAsync(QueueRequestOptions options, throw; } } -#if ASPNET_K +#if ASPNET_K || PORTABLE }, cancellationToken); #else }); @@ -210,7 +209,7 @@ public IAsyncOperation CreateIfNotExistsAsync(QueueRequestOptions options, /// /// Deletes the queue. /// -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync() @@ -228,7 +227,7 @@ public IAsyncAction DeleteAsync() /// /// An object that specifies additional options for the request. /// An object that represents the context for the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task DeleteAsync(QueueRequestOptions options, OperationContext operationContext) @@ -251,7 +250,7 @@ public IAsyncAction DeleteAsync(QueueRequestOptions options, OperationContext op } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Deletes the queue. /// @@ -278,7 +277,7 @@ public Task DeleteAsync(QueueRequestOptions options, OperationContext operationC /// /// true if the queue already existed and was deleted; otherwise, false. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task DeleteIfExistsAsync() #else public IAsyncOperation DeleteIfExistsAsync() @@ -294,7 +293,7 @@ public IAsyncOperation DeleteIfExistsAsync() /// An object that represents the context for the current operation. /// true if the queue already existed and was deleted; otherwise, false. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task DeleteIfExistsAsync(QueueRequestOptions options, OperationContext operationContext) { return this.DeleteIfExistsAsync(options, operationContext, CancellationToken.None); @@ -316,7 +315,7 @@ public IAsyncOperation DeleteIfExistsAsync(QueueRequestOptions options, Op QueueRequestOptions modifiedOptions = QueueRequestOptions.ApplyDefaults(options, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); -#if ASPNET_K +#if ASPNET_K || PORTABLE return Task.Run(async () => { bool exists = await this.ExistsAsync(true, modifiedOptions, operationContext, cancellationToken); @@ -333,7 +332,7 @@ public IAsyncOperation DeleteIfExistsAsync(QueueRequestOptions options, Op try { -#if ASPNET_K +#if ASPNET_K || PORTABLE await this.DeleteAsync(modifiedOptions, operationContext, cancellationToken); #else await this.DeleteAsync(modifiedOptions, operationContext).AsTask(token); @@ -360,18 +359,19 @@ public IAsyncOperation DeleteIfExistsAsync(QueueRequestOptions options, Op throw; } } -#if ASPNET_K +#if ASPNET_K || PORTABLE }, cancellationToken); #else }); #endif } +#if !PORTABLE /// /// Sets permissions for the queue. /// /// The permissions to apply to the queue. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetPermissionsAsync(QueuePermissions permissions) @@ -390,7 +390,7 @@ public IAsyncAction SetPermissionsAsync(QueuePermissions permissions) /// The permissions to apply to the queue. /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetPermissionsAsync(QueuePermissions permissions, QueueRequestOptions options, OperationContext operationContext) @@ -413,7 +413,7 @@ public IAsyncAction SetPermissionsAsync(QueuePermissions permissions, QueueReque } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Sets permissions for the queue. /// @@ -441,7 +441,7 @@ public Task SetPermissionsAsync(QueuePermissions permissions, QueueRequestOption /// /// The queue's permissions. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetPermissionsAsync() #else public IAsyncOperation GetPermissionsAsync() @@ -457,7 +457,7 @@ public IAsyncOperation GetPermissionsAsync() /// An object that represents the context for the current operation. /// The queue's permissions. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetPermissionsAsync(QueueRequestOptions options, OperationContext operationContext) { return this.GetPermissionsAsync(options, operationContext, CancellationToken.None); @@ -476,7 +476,7 @@ public IAsyncOperation GetPermissionsAsync(QueueRequestOptions } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Gets the permissions settings for the queue. /// @@ -496,8 +496,8 @@ public Task GetPermissionsAsync(QueueRequestOptions options, O operationContext, cancellationToken), cancellationToken); } -#endif #endif +#endif /// /// Checks existence of the queue. @@ -648,11 +648,10 @@ public Task FetchAttributesAsync(QueueRequestOptions options, OperationContext o } #endif -#if !PORTABLE /// /// Sets the queue's user-defined metadata. /// -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetMetadataAsync() @@ -670,7 +669,7 @@ public IAsyncAction SetMetadataAsync() /// /// A object that specifies additional options for the request. /// An object that represents the context for the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetMetadataAsync(QueueRequestOptions options, OperationContext operationContext) @@ -693,7 +692,7 @@ public IAsyncAction SetMetadataAsync(QueueRequestOptions options, OperationConte } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Sets the queue's user-defined metadata. /// @@ -713,7 +712,6 @@ public Task SetMetadataAsync(QueueRequestOptions options, OperationContext opera operationContext, cancellationToken), cancellationToken); } -#endif #endif /// @@ -1453,7 +1451,8 @@ private RESTCommand SetMetadataImpl(QueueRequestOptions options) return putCmd; } - + +#if !PORTABLE /// /// Implementation for the SetPermissions method. /// @@ -1512,6 +1511,7 @@ private RESTCommand GetPermissionsImpl(QueueRequestOptions opt return getCmd; } +#endif /// /// Implementation for the AddMessage method. diff --git a/Lib/WindowsRuntime/Queue/CloudQueueClient.cs b/Lib/WindowsRuntime/Queue/CloudQueueClient.cs index c3b98ebe4..46179caf2 100644 --- a/Lib/WindowsRuntime/Queue/CloudQueueClient.cs +++ b/Lib/WindowsRuntime/Queue/CloudQueueClient.cs @@ -87,13 +87,12 @@ internal HttpClientHandler AuthenticationHandler } } -#if !PORTABLE /// /// Returns a result segment containing a collection of queues. /// /// A token returned by a previous listing operation. /// A result segment of queues. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task ListQueuesSegmentedAsync(QueueContinuationToken currentToken) #else public IAsyncOperation ListQueuesSegmentedAsync(QueueContinuationToken currentToken) @@ -108,7 +107,7 @@ public IAsyncOperation ListQueuesSegmentedAsync(QueueContinu /// The queue name prefix. /// A token returned by a previous listing operation. /// A result segment of queues. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task ListQueuesSegmentedAsync(string prefix, QueueContinuationToken currentToken) #else public IAsyncOperation ListQueuesSegmentedAsync(string prefix, QueueContinuationToken currentToken) @@ -129,7 +128,7 @@ public IAsyncOperation ListQueuesSegmentedAsync(string prefi /// An object that specifies additional options for the request. /// An object that represents the context for the current operation. /// A result segment of queues. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task ListQueuesSegmentedAsync(string prefix, QueueListingDetails detailsIncluded, int? maxResults, QueueContinuationToken currentToken, QueueRequestOptions options, OperationContext operationContext) { return this.ListQueuesSegmentedAsync(prefix, detailsIncluded, maxResults, currentToken, options, operationContext, CancellationToken.None); @@ -153,7 +152,7 @@ public IAsyncOperation ListQueuesSegmentedAsync(string prefi } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Returns a result segment containing a collection of queues /// whose names begin with the specified prefix. @@ -245,7 +244,7 @@ private RESTCommand> ListQueuesImpl(string prefix, int /// /// The blob service properties. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetServicePropertiesAsync() #else public IAsyncOperation GetServicePropertiesAsync() @@ -261,7 +260,7 @@ public IAsyncOperation GetServicePropertiesAsync() /// An object that represents the context for the current operation. /// The blob service properties. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetServicePropertiesAsync(QueueRequestOptions options, OperationContext operationContext) { return this.GetServicePropertiesAsync(options, operationContext, CancellationToken.None); @@ -280,7 +279,7 @@ public IAsyncOperation GetServicePropertiesAsync(QueueRequest } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Gets the properties of the blob service. /// @@ -328,7 +327,7 @@ private RESTCommand GetServicePropertiesImpl(QueueRequestOpti /// The queue service properties. /// The blob service properties. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task SetServicePropertiesAsync(ServiceProperties properties) #else public IAsyncAction SetServicePropertiesAsync(ServiceProperties properties) @@ -345,7 +344,7 @@ public IAsyncAction SetServicePropertiesAsync(ServiceProperties properties) /// An object that represents the context for the current operation. /// The blob service properties. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task SetServicePropertiesAsync(ServiceProperties properties, QueueRequestOptions requestOptions, OperationContext operationContext) { return this.SetServicePropertiesAsync(properties, requestOptions, operationContext, CancellationToken.None); @@ -363,7 +362,7 @@ public IAsyncAction SetServicePropertiesAsync(ServiceProperties properties, Queu } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Gets the properties of the blob service. /// @@ -416,7 +415,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert /// /// The queue service stats. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetServiceStatsAsync() #else public IAsyncOperation GetServiceStatsAsync() @@ -432,7 +431,7 @@ public IAsyncOperation GetServiceStatsAsync() /// An object that represents the context for the current operation. /// The queue service stats. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetServiceStatsAsync(QueueRequestOptions options, OperationContext operationContext) { return this.GetServiceStatsAsync(options, operationContext, CancellationToken.None); @@ -452,7 +451,7 @@ public IAsyncOperation GetServiceStatsAsync(QueueRequestOptions op } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Gets the stats of the queue service. /// @@ -490,6 +489,5 @@ private RESTCommand GetServiceStatsImpl(QueueRequestOptions reques } #endregion -#endif } } diff --git a/Lib/WindowsRuntime/Shared/Protocol/HttpRequestMessageFactory.cs b/Lib/WindowsRuntime/Shared/Protocol/HttpRequestMessageFactory.cs index c641d43e7..18887f5b8 100644 --- a/Lib/WindowsRuntime/Shared/Protocol/HttpRequestMessageFactory.cs +++ b/Lib/WindowsRuntime/Shared/Protocol/HttpRequestMessageFactory.cs @@ -44,7 +44,7 @@ internal static HttpRequestMessage CreateRequestMessage(HttpMethod method, Uri u builder.Add("timeout", timeout.ToString()); } -#if WINDOWS_PHONE +#if WINDOWS_RT && !ASPNET_K // Windows Phone does not allow the caller to disable caching, so a random GUID // is added to every URI to make it look like a different request. builder.Add("randomguid", Guid.NewGuid().ToString("N")); diff --git a/Lib/WindowsRuntime/Table/CloudTable.cs b/Lib/WindowsRuntime/Table/CloudTable.cs index b94650c02..47880872d 100644 --- a/Lib/WindowsRuntime/Table/CloudTable.cs +++ b/Lib/WindowsRuntime/Table/CloudTable.cs @@ -212,13 +212,12 @@ public Task ExecuteQuerySegmentedAsync(TableQuery query, Tabl #endif #endregion -#if !PORTABLE - #region Create +#region Create /// /// Creates the Table. /// -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. public Task CreateAsync() #else @@ -234,7 +233,7 @@ public IAsyncAction CreateAsync() /// /// A object that specifies additional options for the request. /// An object for tracking the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. public Task CreateAsync(TableRequestOptions requestOptions, OperationContext operationContext) { @@ -262,7 +261,7 @@ public IAsyncAction CreateAsync(TableRequestOptions requestOptions, OperationCon TableOperation operation = new TableOperation(tblEntity, TableOperationType.Insert, false); operation.IsTableEntity = true; -#if ASPNET_K +#if ASPNET_K || PORTABLE return this.ServiceClient.ExecuteAsync(TableConstants.TableServiceTablesName, operation, requestOptions, operationContext, cancellationToken); #else return this.ServiceClient.ExecuteAsync(TableConstants.TableServiceTablesName, operation, requestOptions, operationContext).AsTask().AsAsyncAction(); @@ -277,7 +276,7 @@ public IAsyncAction CreateAsync(TableRequestOptions requestOptions, OperationCon /// Creates the table if it does not already exist. /// /// true if table was created; otherwise, false. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task CreateIfNotExistsAsync() #else public IAsyncOperation CreateIfNotExistsAsync() @@ -292,7 +291,7 @@ public IAsyncOperation CreateIfNotExistsAsync() /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. /// true if table was created; otherwise, false. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task CreateIfNotExistsAsync(TableRequestOptions requestOptions, OperationContext operationContext) { return this.CreateIfNotExistsAsync(requestOptions, operationContext, CancellationToken.None); @@ -313,7 +312,7 @@ public IAsyncOperation CreateIfNotExistsAsync(TableRequestOptions requestO requestOptions = TableRequestOptions.ApplyDefaults(requestOptions, this.ServiceClient); operationContext = operationContext ?? new OperationContext(); -#if ASPNET_K +#if ASPNET_K || PORTABLE return Task.Run(async () => { if (await this.ExistsAsync(true, requestOptions, operationContext, cancellationToken)) @@ -329,7 +328,7 @@ public IAsyncOperation CreateIfNotExistsAsync(TableRequestOptions requestO { try { -#if ASPNET_K +#if ASPNET_K || PORTABLE await this.CreateAsync(requestOptions, operationContext, cancellationToken); #else await this.CreateAsync(requestOptions, operationContext).AsTask(cancellationToken); @@ -357,7 +356,7 @@ public IAsyncOperation CreateIfNotExistsAsync(TableRequestOptions requestO } } } -#if ASPNET_K +#if ASPNET_K || PORTABLE }, cancellationToken); #else }); @@ -370,7 +369,7 @@ public IAsyncOperation CreateIfNotExistsAsync(TableRequestOptions requestO /// /// Deletes the Table. /// -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. public Task DeleteAsync() #else @@ -386,7 +385,7 @@ public IAsyncAction DeleteAsync() /// /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. public Task DeleteAsync(TableRequestOptions requestOptions, OperationContext operationContext) { @@ -414,7 +413,7 @@ public IAsyncAction DeleteAsync(TableRequestOptions requestOptions, OperationCon TableOperation operation = new TableOperation(tblEntity, TableOperationType.Delete); operation.IsTableEntity = true; -#if ASPNET_K +#if ASPNET_K || PORTABLE return this.ServiceClient.ExecuteAsync(TableConstants.TableServiceTablesName, operation, requestOptions, operationContext, cancellationToken); #else return this.ServiceClient.ExecuteAsync(TableConstants.TableServiceTablesName, operation, requestOptions, operationContext).AsTask().AsAsyncAction(); @@ -428,7 +427,7 @@ public IAsyncAction DeleteAsync(TableRequestOptions requestOptions, OperationCon /// Deletes the table if it already exists. /// /// true if the table already existed and was deleted; otherwise, false. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task DeleteIfExistsAsync() #else public IAsyncOperation DeleteIfExistsAsync() @@ -443,7 +442,7 @@ public IAsyncOperation DeleteIfExistsAsync() /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. /// true if the table already existed and was deleted; otherwise, false. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task DeleteIfExistsAsync(TableRequestOptions requestOptions, OperationContext operationContext) { return this.DeleteIfExistsAsync(requestOptions, operationContext, CancellationToken.None); @@ -465,7 +464,7 @@ public IAsyncOperation DeleteIfExistsAsync(TableRequestOptions requestOpti operationContext = operationContext ?? new OperationContext(); -#if ASPNET_K +#if ASPNET_K || PORTABLE return Task.Run(async () => { if (!await this.ExistsAsync(true, requestOptions, operationContext, cancellationToken)) @@ -481,7 +480,7 @@ public IAsyncOperation DeleteIfExistsAsync(TableRequestOptions requestOpti { try { -#if ASPNET_K +#if ASPNET_K || PORTABLE await this.DeleteAsync(requestOptions, operationContext, cancellationToken); #else await this.DeleteAsync(requestOptions, operationContext).AsTask(cancellationToken); @@ -509,14 +508,13 @@ public IAsyncOperation DeleteIfExistsAsync(TableRequestOptions requestOpti } } } -#if ASPNET_K +#if ASPNET_K || PORTABLE }, cancellationToken); #else }); #endif } #endregion -#endif #region Exists /// @@ -613,7 +611,7 @@ private IAsyncOperation ExistsAsync(bool primaryOnly, TableRequestOptions /// Sets permissions for the Table. /// /// The permissions to apply to the Table. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. public Task SetPermissionsAsync(TablePermissions permissions) #else @@ -631,7 +629,7 @@ public IAsyncAction SetPermissionsAsync(TablePermissions permissions) /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. /// An that represents an asynchronous action. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task SetPermissionsAsync(TablePermissions permissions, TableRequestOptions requestOptions, OperationContext operationContext) { return this.SetPermissionsAsync(permissions, requestOptions, operationContext, CancellationToken.None); @@ -650,7 +648,7 @@ public IAsyncAction SetPermissionsAsync(TablePermissions permissions, TableReque } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Sets permissions for the Table. /// @@ -705,7 +703,7 @@ private RESTCommand SetPermissionsImpl(TablePermissions acl, TableRequ /// Gets the permissions settings for the Table. /// /// The Table's permissions. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetPermissionsAsync() #else public IAsyncOperation GetPermissionsAsync() @@ -720,7 +718,7 @@ public IAsyncOperation GetPermissionsAsync() /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. /// The Table's permissions. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetPermissionsAsync(TableRequestOptions requestOptions, OperationContext operationContext) { return this.GetPermissionsAsync(requestOptions, operationContext, CancellationToken.None); @@ -739,7 +737,7 @@ public IAsyncOperation GetPermissionsAsync(TableRequestOptions } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Gets the permissions settings for the Table. /// diff --git a/Lib/WindowsRuntime/Table/CloudTableClient.cs b/Lib/WindowsRuntime/Table/CloudTableClient.cs index c8d68fda0..ffcc97fad 100644 --- a/Lib/WindowsRuntime/Table/CloudTableClient.cs +++ b/Lib/WindowsRuntime/Table/CloudTableClient.cs @@ -130,7 +130,6 @@ internal Task ExecuteQuerySegmentedAsync(string tableName, Ta #endregion #region List Tables -#if !PORTABLE private TableQuery GenerateListTablesQuery(string prefix, int? maxResults) { TableQuery query = new TableQuery(); @@ -178,7 +177,7 @@ internal IEnumerable ListTables(string prefix, TableRequestOptions r /// /// A token returned by a previous listing operation. /// The result segment containing the collection of tables. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task ListTablesSegmentedAsync(TableContinuationToken currentToken) #else public IAsyncOperation ListTablesSegmentedAsync(TableContinuationToken currentToken) @@ -193,7 +192,7 @@ public IAsyncOperation ListTablesSegmentedAsync(TableContinu /// The table name prefix. /// A token returned by a previous listing operation. /// The result segment containing the collection of tables. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task ListTablesSegmentedAsync(string prefix, TableContinuationToken currentToken) #else public IAsyncOperation ListTablesSegmentedAsync(string prefix, TableContinuationToken currentToken) @@ -212,7 +211,7 @@ public IAsyncOperation ListTablesSegmentedAsync(string prefi /// A object that specifies additional options for the request. /// An object that provides information on how the operation executed. /// The result segment containing the collection of tables. -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task ListTablesSegmentedAsync(string prefix, int? maxResults, TableContinuationToken currentToken, TableRequestOptions requestOptions, OperationContext operationContext) { return this.ListTablesSegmentedAsync(prefix, maxResults, currentToken, requestOptions, operationContext, CancellationToken.None); @@ -239,7 +238,7 @@ public IAsyncOperation ListTablesSegmentedAsync(string prefi TableQuery query = this.GenerateListTablesQuery(prefix, maxResults); -#if ASPNET_K +#if ASPNET_K || PORTABLE return Task.Run(async () => { TableQuerySegment seg = await this.ExecuteQuerySegmentedAsync(TableConstants.TableServiceTablesName, query, currentToken, requestOptions, operationContext, cancellationToken); @@ -252,23 +251,21 @@ public IAsyncOperation ListTablesSegmentedAsync(string prefi TableResultSegment retSegment = new TableResultSegment(seg.Results.Select(tbl => new CloudTable(tbl.Properties[TableConstants.TableName].StringValue, this)).ToList()); retSegment.ContinuationToken = seg.ContinuationToken; return retSegment; -#if ASPNET_K +#if ASPNET_K || PORTABLE }, cancellationToken); #else }); #endif } -#endif #endregion #region Analytics -#if !PORTABLE /// /// Gets the properties of the table service. /// /// The table service properties as a object. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetServicePropertiesAsync() #else public IAsyncOperation GetServicePropertiesAsync() @@ -284,7 +281,7 @@ public IAsyncOperation GetServicePropertiesAsync() /// An object for tracking the current operation. /// The table service properties as a object. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetServicePropertiesAsync(TableRequestOptions requestOptions, OperationContext operationContext) { return this.GetServicePropertiesAsync(requestOptions, operationContext, CancellationToken.None); @@ -303,7 +300,7 @@ public IAsyncOperation GetServicePropertiesAsync(TableRequest } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Gets the properties of the table service. /// @@ -352,7 +349,7 @@ private RESTCommand GetServicePropertiesImpl(TableRequestOpti /// /// The table service properties. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. public Task SetServicePropertiesAsync(ServiceProperties properties) #else @@ -369,7 +366,7 @@ public IAsyncAction SetServicePropertiesAsync(ServiceProperties properties) /// The table service properties. /// A object that specifies execution options, such as retry policy and timeout settings, for the operation. /// An object for tracking the current operation. -#if ASPNET_K +#if ASPNET_K || PORTABLE /// A that represents an asynchronous action. [DoesServiceRequest] public Task SetServicePropertiesAsync(ServiceProperties properties, TableRequestOptions requestOptions, OperationContext operationContext) @@ -391,7 +388,7 @@ public IAsyncAction SetServicePropertiesAsync(ServiceProperties properties, Tabl } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Gets the properties of the table service. /// @@ -445,7 +442,7 @@ private RESTCommand SetServicePropertiesImpl(ServiceProperties propert /// /// The table service stats. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetServiceStatsAsync() #else public IAsyncOperation GetServiceStatsAsync() @@ -461,7 +458,7 @@ public IAsyncOperation GetServiceStatsAsync() /// An object that represents the context for the current operation. /// The table service stats. [DoesServiceRequest] -#if ASPNET_K +#if ASPNET_K || PORTABLE public Task GetServiceStatsAsync(TableRequestOptions options, OperationContext operationContext) { return this.GetServiceStatsAsync(options, operationContext, CancellationToken.None); @@ -481,7 +478,7 @@ public IAsyncOperation GetServiceStatsAsync(TableRequestOptions op } #endif -#if ASPNET_K +#if ASPNET_K || PORTABLE /// /// Gets the stats of the table service. /// @@ -519,7 +516,6 @@ private RESTCommand GetServiceStatsImpl(TableRequestOptions reques retCmd.PostProcessResponse = (cmd, resp, ctx) => Task.Factory.StartNew(() => HttpResponseParsers.ReadServiceStats(cmd.ResponseStream)); return retCmd; } -#endif #endregion } } diff --git a/Lib/WindowsRuntime/Table/TableBatchOperation.cs b/Lib/WindowsRuntime/Table/TableBatchOperation.cs index 4a7d29dc2..0e868b3e2 100644 --- a/Lib/WindowsRuntime/Table/TableBatchOperation.cs +++ b/Lib/WindowsRuntime/Table/TableBatchOperation.cs @@ -97,13 +97,14 @@ private static RESTCommand> BatchImpl(TableBatchOperation bat /// The input , which acts as the this instance for the extension method. /// A string containing the partition key of the entity to be retrieved. /// A string containing the row key of the entity to be retrieved. - public void Retrieve(string partitionKey, string rowkey) where TElement : ITableEntity + /// List of column names for projection. + public void Retrieve(string partitionKey, string rowkey, List selectedColumns = null) where TElement : ITableEntity { CommonUtility.AssertNotNull("partitionKey", partitionKey); CommonUtility.AssertNotNull("rowkey", rowkey); // Add the table operation. - this.Add(new TableOperation(null /* entity */, TableOperationType.Retrieve) { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowkey, RetrieveResolver = (pk, rk, ts, prop, etag) => EntityUtilities.ResolveEntityByType(pk, rk, ts, prop, etag) }); + this.Add(new TableOperation(null /* entity */, TableOperationType.Retrieve) { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowkey, SelectColumns = selectedColumns, RetrieveResolver = (pk, rk, ts, prop, etag) => EntityUtilities.ResolveEntityByType(pk, rk, ts, prop, etag) }); } /// @@ -114,13 +115,14 @@ public void Retrieve(string partitionKey, string rowkey) where TElemen /// A string containing the partition key of the entity to be retrieved. /// A string containing the row key of the entity to be retrieved. /// The implementation to project the entity to retrieve as a particular type in the result. - public void Retrieve(string partitionKey, string rowkey, EntityResolver resolver) + /// List of column names for projection. + public void Retrieve(string partitionKey, string rowkey, EntityResolver resolver, List selectedColumns = null) { CommonUtility.AssertNotNull("partitionKey", partitionKey); CommonUtility.AssertNotNull("rowkey", rowkey); // Add the table operation. - this.Add(new TableOperation(null /* entity */, TableOperationType.Retrieve) { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowkey, RetrieveResolver = (pk, rk, ts, prop, etag) => resolver(pk, rk, ts, prop, etag) }); + this.Add(new TableOperation(null /* entity */, TableOperationType.Retrieve) { RetrievePartitionKey = partitionKey, RetrieveRowKey = rowkey, SelectColumns = selectedColumns, RetrieveResolver = (pk, rk, ts, prop, etag) => resolver(pk, rk, ts, prop, etag) }); } } } diff --git a/Lib/WindowsRuntime/Table/TableOperation.cs b/Lib/WindowsRuntime/Table/TableOperation.cs index 2d438a475..84b2f6d82 100644 --- a/Lib/WindowsRuntime/Table/TableOperation.cs +++ b/Lib/WindowsRuntime/Table/TableOperation.cs @@ -185,6 +185,11 @@ private static RESTCommand RetrieveImpl(TableOperation operation, C requestOptions.ApplyToStorageCommand(retrieveCmd); TableResult result = new TableResult(); + if (operation.SelectColumns != null && operation.SelectColumns.Count > 0) + { + retrieveCmd.Builder = operation.GenerateQueryBuilder(); + } + retrieveCmd.CommandLocationMode = operation.isPrimaryOnlyRetrieve ? CommandLocationMode.PrimaryOnly : CommandLocationMode.PrimaryOrSecondary; retrieveCmd.RetrieveResponseStream = true; retrieveCmd.Handler = client.AuthenticationHandler; diff --git a/README.md b/README.md index 64471d98b..809fb0faa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Microsoft Azure Storage SDK for .NET (6.0.0) +# Microsoft Azure Storage SDK for .NET (6.1.0) The Microsoft Azure Storage SDK for .NET allows you to build Azure applications that take advantage of scalable cloud computing resources. @@ -32,7 +32,7 @@ For the best development experience, developers should use the official Microsof - .NET Framework 4.0: As of October 2012, Storage Client Libraries for .NET supports primarily the desktop .NET Framework 4 release and above. - Windows 8 and 8.1 for Windows Store app development: Storage Client Libraries are available for Windows Store applications. - Windows Phone 8 and 8.1 app development: Storage Client Libraries are available for Windows Phone applications including Universal applications. -- DNX 4.5.1 and DNXCore 5.0, beta7: Storage Client Libraries for .NET are available to support ASP.NET 5 application development. This support is currently in preview. +- DNX 4.5.1 and DNXCore 5.0, beta8: Storage Client Libraries for .NET are available to support ASP.NET 5 application development. This support is currently in preview. - Portable Class Library Profile 111: Storage Client Libraries are available to support building PCL Profile 111 applications, as well as for Xamarin application development. This support is currently in preview. ## Requirements diff --git a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json index 251912e5e..1c0acbada 100644 --- a/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json +++ b/Test/AspNet/Microsoft.WindowsAzure.Storage.Test/project.json @@ -1,5 +1,5 @@ { - "version": "6.0.1.0", + "version": "6.1.1.0", "dependencies": { "xunit": "2.1.0-*", "xunit.runner.dnx": "2.1.0-*", diff --git a/Test/AspNet/XUnitForMsTest/project.json b/Test/AspNet/XUnitForMsTest/project.json index 9207f8714..f2d460c21 100644 --- a/Test/AspNet/XUnitForMsTest/project.json +++ b/Test/AspNet/XUnitForMsTest/project.json @@ -18,7 +18,7 @@ }, "dnxcore50": { "dependencies": { - "System.Runtime": "4.0.10-beta-*" + "System.Runtime": "4.0.20-beta-*" } } }, diff --git a/Test/ClassLibraryCommon/Blob/BlobEncryptionTests.cs b/Test/ClassLibraryCommon/Blob/BlobEncryptionTests.cs index 007d848f1..a5d93577d 100644 --- a/Test/ClassLibraryCommon/Blob/BlobEncryptionTests.cs +++ b/Test/ClassLibraryCommon/Blob/BlobEncryptionTests.cs @@ -81,7 +81,7 @@ private void DoCloudBlobEncryption(BlobType type, bool partial) container.Create(); int size = 5 * 1024 * 1024; byte[] buffer = GetRandomBuffer(size); - + if (partial) { size = 2 * 1024 * 1024; @@ -759,10 +759,10 @@ private void ValidateRangeDecryption(BlobType type, int blobSize, int? blobOffse // Compare that the decrypted contents match the input data. byte[] outputArray = outputStream.ToArray(); - + for (int i = 0; i < outputArray.Length; i++) { - int bufferOffset = (int) (blobOffset.HasValue ? blobOffset : 0); + int bufferOffset = (int)(blobOffset.HasValue ? blobOffset : 0); Assert.AreEqual(buffer[bufferOffset + i], outputArray[i]); } } @@ -1182,7 +1182,7 @@ private void DoCloudBlobEncryptionWithStrictMode(BlobType type) uploadOptions.EncryptionPolicy = null; stream = new MemoryStream(buffer); - TestHelper.ExpectedException( + TestHelper.ExpectedException( () => blob.UploadFromStream(stream, size, null, uploadOptions, null), "Not specifying a policy when RequireEnryption is set to true should throw."); @@ -1223,7 +1223,7 @@ private void DoCloudBlobEncryptionWithStrictMode(BlobType type) [TestMethod] [Description("Validate partial blob encryption with RequireEncryption flag.")] - [TestCategory(ComponentCategory.Table)] + [TestCategory(ComponentCategory.Blob)] [TestCategory(TestTypeCategory.UnitTest)] [TestCategory(SmokeTestCategory.NonSmoke)] [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] @@ -1332,5 +1332,191 @@ private static ICloudBlob GetCloudBlobReference(BlobType type, CloudBlobContaine return blob; } + + [TestMethod] + [Description("Validate that the bug where we did not encrypt data if certain access conditions were specified no longer exists.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlobEncryptionOpenWriteStreamAPMWithAccessCondition() + { + CloudBlobContainer container = GetRandomContainerReference(); + + try + { + int size = (int)(1 * Constants.MB); + container.Create(); + byte[] buffer = GetRandomBuffer(size); + + CloudBlockBlob blob = container.GetBlockBlobReference("blockblob"); + blob.StreamWriteSizeInBytes = (int)(4 * Constants.MB); + + blob.UploadText("Sample initial text"); + + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); + + // Create the encryption policy to be used for upload. + BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(aesKey, null); + + // Set the encryption policy on the request options. + BlobRequestOptions uploadOptions = new BlobRequestOptions() + { + EncryptionPolicy = uploadPolicy, + RequireEncryption = true + }; + + AccessCondition accessCondition = AccessCondition.GenerateIfMatchCondition(blob.Properties.ETag); + + using (MemoryStream stream = new NonSeekableMemoryStream(buffer)) + { + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + blob.EndUploadFromStream(blob.BeginUploadFromStream( + stream, size, accessCondition, uploadOptions, null, ar => waitHandle.Set(), null)); + } + } + + // Download the encrypted blob. + // Create the decryption policy to be used for download. There is no need to specify the + // key when the policy is only going to be used for downloads. Resolver is sufficient. + BlobEncryptionPolicy downloadPolicy = new BlobEncryptionPolicy(null, resolver); + + // Set the decryption policy on the request options. + BlobRequestOptions downloadOptions = new BlobRequestOptions() + { + EncryptionPolicy = downloadPolicy, + RequireEncryption = true + }; + + // Download and decrypt the encrypted contents from the blob. + MemoryStream outputStream = new MemoryStream(); + blob.DownloadToStream(outputStream, null, downloadOptions, null); + + // Ensure that the user stream is open. + outputStream.Seek(0, SeekOrigin.Begin); + + // Compare that the decrypted contents match the input data. + byte[] outputArray = outputStream.ToArray(); + TestHelper.AssertBuffersAreEqualUptoIndex(outputArray, buffer, size - 1); + } + finally + { + container.DeleteIfExists(); + } + } + + [TestMethod] + [Description("Validate that we will do a single PutBlob call when uplaoding an encrypted blob if the data is short enough.")] + [TestCategory(ComponentCategory.Blob)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudBlobEncryptionCountOperations() + { + BlobRequestOptions options = new BlobRequestOptions(); + options.SingleBlobUploadThresholdInBytes = 8 * Constants.MB - 3; // We should test a non-multiple of 16 + options.ParallelOperationThreadCount = 1; + + Boolean[] bothBools = new Boolean[] { true, false }; + + foreach (bool isAPM in bothBools) + { + foreach (bool calculateMD5 in bothBools) + { + options.StoreBlobContentMD5 = calculateMD5; + options.UseTransactionalMD5 = calculateMD5; + options.DisableContentMD5Validation = !calculateMD5; + this.CountOperationsHelper(10, 1, true, isAPM, options); + this.CountOperationsHelper((int)(1 * Constants.MB), 1, true, isAPM, options); + + // This one should not call put, because encryption padding will put it over length. + this.CountOperationsHelper((int)(options.SingleBlobUploadThresholdInBytes - 2), 3, true, isAPM, options); + this.CountOperationsHelper((int)(13 * Constants.MB), 5, true, isAPM, options); + } + } + } + + private void CountOperationsHelper(int size, int targetUploadOperations, bool streamSeekable, bool isAPM, BlobRequestOptions options) + { + CloudBlobContainer container = GetRandomContainerReference(); + + try + { + container.Create(); + byte[] buffer = GetRandomBuffer(size); + + CloudBlockBlob blob = container.GetBlockBlobReference("blockblob"); + blob.StreamWriteSizeInBytes = (int)(4 * Constants.MB); + + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); + + // Create the encryption policy to be used for upload. + BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(aesKey, null); + + // Set the encryption policy on the request options. + options.EncryptionPolicy = uploadPolicy; + OperationContext opContext = new OperationContext(); + + int uploadCount = 0; + opContext.SendingRequest += (sender, e) => uploadCount++; + + using (MemoryStream stream = streamSeekable ? new MemoryStream(buffer) : new NonSeekableMemoryStream(buffer)) + { + if (isAPM) + { + using (AutoResetEvent waitHandle = new AutoResetEvent(false)) + { + blob.EndUploadFromStream(blob.BeginUploadFromStream( + stream, size, null, options, opContext, ar => waitHandle.Set(), null)); + } + } + else + { + blob.UploadFromStream(stream, size, null, options, opContext); + } + + // Ensure that the user stream is open if it's seekable. + if (streamSeekable) + { + Assert.IsTrue(stream.CanSeek); + } + } + + // Download the encrypted blob. + // Create the decryption policy to be used for download. There is no need to specify the + // key when the policy is only going to be used for downloads. Resolver is sufficient. + BlobEncryptionPolicy downloadPolicy = new BlobEncryptionPolicy(null, resolver); + + // Set the decryption policy on the request options. + BlobRequestOptions downloadOptions = new BlobRequestOptions() { EncryptionPolicy = downloadPolicy }; + + // Download and decrypt the encrypted contents from the blob. + MemoryStream outputStream = new MemoryStream(); + blob.DownloadToStream(outputStream, null, downloadOptions, null); + + // Ensure that the user stream is open. + outputStream.Seek(0, SeekOrigin.Begin); + + // Compare that the decrypted contents match the input data. + byte[] outputArray = outputStream.ToArray(); + TestHelper.AssertBuffersAreEqualUptoIndex(outputArray, buffer, size - 1); + Assert.AreEqual(targetUploadOperations, uploadCount, "Incorrect number of operations in encrypted blob upload."); + } + finally + { + container.DeleteIfExists(); + } + } } } diff --git a/Test/ClassLibraryCommon/Blob/BlobManglerUnitTests.cs b/Test/ClassLibraryCommon/Blob/BlobManglerUnitTests.cs index 76bfc528f..28e9d22ae 100644 --- a/Test/ClassLibraryCommon/Blob/BlobManglerUnitTests.cs +++ b/Test/ClassLibraryCommon/Blob/BlobManglerUnitTests.cs @@ -50,6 +50,7 @@ public void MyTestCleanup() } } + [Ignore] [TestMethod] [Description("Validate the Ingress Egress Counters for blob operations")] [TestCategory(ComponentCategory.Blob)] diff --git a/Test/ClassLibraryCommon/Blob/SASTests.cs b/Test/ClassLibraryCommon/Blob/SASTests.cs index 659aafb5a..af2acc420 100644 --- a/Test/ClassLibraryCommon/Blob/SASTests.cs +++ b/Test/ClassLibraryCommon/Blob/SASTests.cs @@ -97,6 +97,8 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm } } + HttpStatusCode failureCode = sasToken == null ? HttpStatusCode.NotFound : HttpStatusCode.Forbidden; + // We want to ensure that 'create', 'add', and 'write' permissions all allow for correct writing of blobs, as is reasonable. if (((permissions & SharedAccessBlobPermissions.Create) == SharedAccessBlobPermissions.Create) || ((permissions & SharedAccessBlobPermissions.Write) == SharedAccessBlobPermissions.Write)) { @@ -117,7 +119,7 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm TestHelper.ExpectedException( () => SASpageBlob.UploadFromByteArray(buffer, 0, 512), "pageBlob SAS token without Write perms should not allow for writing/adding", - HttpStatusCode.Forbidden); + failureCode); pageBlob.UploadFromByteArray(buffer, 0, 512); } } @@ -132,7 +134,7 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm TestHelper.ExpectedException( () => UploadText(SASblob, "blob", Encoding.UTF8), "Block blob SAS token without Write or perms should not allow for writing", - HttpStatusCode.Forbidden); + failureCode); UploadText(blob, "blob", Encoding.UTF8); } } @@ -163,7 +165,7 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm TestHelper.ExpectedException( () => SASAppendBlob.AppendBlock(stream, null), "Append blob SAS token without Write or Add perms should not allow for writing/adding", - HttpStatusCode.Forbidden); + failureCode); stream.Seek(0, SeekOrigin.Begin); ((CloudAppendBlob)blob).AppendBlock(stream, null); } @@ -176,7 +178,7 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm TestHelper.ExpectedException( () => UploadText(SASblob, "blob", Encoding.UTF8), "UploadText SAS does not allow for writing/adding", - ((blob.BlobType == BlobType.AppendBlob) && (sasToken != null) && (sasToken.Contains(Constants.VersionConstants.February2012))) ? HttpStatusCode.BadRequest : HttpStatusCode.Forbidden); + ((blob.BlobType == BlobType.AppendBlob) && (sasToken != null) && (sasToken.Contains(Constants.VersionConstants.February2012))) ? HttpStatusCode.BadRequest : failureCode); UploadText(blob, "blob", Encoding.UTF8); } @@ -191,7 +193,7 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm TestHelper.ExpectedException( () => container.ListBlobs().ToArray(), "List blobs while SAS does not allow for listing", - HttpStatusCode.Forbidden); + failureCode); } } @@ -234,7 +236,7 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm TestHelper.ExpectedException( () => SASblob.FetchAttributes(), "Fetch blob attributes while SAS does not allow for reading", - HttpStatusCode.Forbidden); + failureCode); } if ((permissions & SharedAccessBlobPermissions.Write) == SharedAccessBlobPermissions.Write) @@ -246,7 +248,7 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm TestHelper.ExpectedException( () => SASblob.SetMetadata(), "Set blob metadata while SAS does not allow for writing", - HttpStatusCode.Forbidden); + failureCode); } if ((permissions & SharedAccessBlobPermissions.Delete) == SharedAccessBlobPermissions.Delete) @@ -258,7 +260,7 @@ private static void TestAccess(string sasToken, SharedAccessBlobPermissions perm TestHelper.ExpectedException( () => SASblob.Delete(), "Delete blob while SAS does not allow for deleting", - HttpStatusCode.Forbidden); + failureCode); } } diff --git a/Test/ClassLibraryCommon/File/FileUploadDownloadTest.cs b/Test/ClassLibraryCommon/File/FileUploadDownloadTest.cs index aafd1b9d4..ccdc36d22 100644 --- a/Test/ClassLibraryCommon/File/FileUploadDownloadTest.cs +++ b/Test/ClassLibraryCommon/File/FileUploadDownloadTest.cs @@ -147,6 +147,7 @@ public void FileUploadWithoutMD5ValidationAndStoreFileContentTest() } } + [Ignore] [TestMethod] [Description("Upload from text to a file")] [TestCategory(ComponentCategory.File)] diff --git a/Test/ClassLibraryCommon/File/Protocol/FileProtocolTest.cs b/Test/ClassLibraryCommon/File/Protocol/FileProtocolTest.cs index 4e5084bc1..b138dbc61 100644 --- a/Test/ClassLibraryCommon/File/Protocol/FileProtocolTest.cs +++ b/Test/ClassLibraryCommon/File/Protocol/FileProtocolTest.cs @@ -271,6 +271,7 @@ public void FileProtocolListSharesCloud() } } + [Ignore] [TestMethod] [Description("Get a share with empty header excluded/included from signature and verify request succeeded")] [TestCategory(ComponentCategory.File)] diff --git a/Test/ClassLibraryCommon/Table/TableEntityEncryptionTests.cs b/Test/ClassLibraryCommon/Table/TableEntityEncryptionTests.cs index 664dd63e4..aa7ebc596 100644 --- a/Test/ClassLibraryCommon/Table/TableEntityEncryptionTests.cs +++ b/Test/ClassLibraryCommon/Table/TableEntityEncryptionTests.cs @@ -423,6 +423,48 @@ private void DoTableQueryPOCOProjectionEncryption(TablePayloadFormat format, Sym } } + [TestMethod] + [Description("Checks to ensure that when querying with encryption, if we don't request any specific columns, we get all the data back.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableQueryProjectionEncryptionNoSelect() + { + // Insert Entity + EncryptedBaseEntity ent1 = new EncryptedBaseEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; + ent1.Populate(); + + EncryptedBaseEntity ent2 = new EncryptedBaseEntity() { PartitionKey = Guid.NewGuid().ToString(), RowKey = DateTime.Now.Ticks.ToString() }; + ent2.Populate(); + + // Create the Key to be used for wrapping. + SymmetricKey aesKey = new SymmetricKey("symencryptionkey"); + + TableRequestOptions options = new TableRequestOptions() { EncryptionPolicy = new TableEncryptionPolicy(aesKey, null) }; + currentTable.Execute(TableOperation.Insert(ent1), options, null); + currentTable.Execute(TableOperation.Insert(ent2), options, null); + + tableClient.DefaultRequestOptions.PayloadFormat = TablePayloadFormat.Json; + + // Create the resolver to be used for unwrapping. + DictionaryKeyResolver resolver = new DictionaryKeyResolver(); + resolver.Add(aesKey); + TableEncryptionPolicy encryptionPolicy = new TableEncryptionPolicy(null, resolver); + + IEnumerable entities = null; + CloudTableClient encryptingTableClient = new CloudTableClient(this.tableClient.StorageUri, this.tableClient.Credentials); + encryptingTableClient.DefaultRequestOptions.EncryptionPolicy = encryptionPolicy; + encryptingTableClient.DefaultRequestOptions.RequireEncryption = true; + + entities = encryptingTableClient.GetTableReference(currentTable.Name).CreateQuery().Select(ent => ent); + + foreach (EncryptedBaseEntity ent in entities) + { + ent.Validate(); + } + } + [TestMethod] [Description("Basic projection test with encryption")] [TestCategory(ComponentCategory.Table)] diff --git a/Test/ClassLibraryCommon/Table/TableOperationUnitTests.cs b/Test/ClassLibraryCommon/Table/TableOperationUnitTests.cs index 9b49391c7..0705fd5c3 100644 --- a/Test/ClassLibraryCommon/Table/TableOperationUnitTests.cs +++ b/Test/ClassLibraryCommon/Table/TableOperationUnitTests.cs @@ -1622,6 +1622,300 @@ private void DoTableRetrieveSync(TablePayloadFormat format) Assert.AreEqual(sendEnt["DateTimeN"], retrievedEntity["DateTimeN"]); } + [TestMethod] + [Description("A test to check retrieve projection functionality selecting all valid columns.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableRetrieveWithFullProjectionSync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableRetrieveWithFullProjectionSync(payloadFormat); + } + } + + private void DoTableRetrieveWithFullProjectionSync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert entity + DynamicTableEntity sendEnt = new DynamicTableEntity(); + sendEnt.PartitionKey = Guid.NewGuid().ToString(); + sendEnt.RowKey = Guid.NewGuid().ToString(); + sendEnt.Properties = new ComplexEntity().WriteEntity(null); + currentTable.Execute(TableOperation.Insert(sendEnt)); + + TableRequestOptions options = new TableRequestOptions() + { + PropertyResolver = (partitionKey, rowKey, propName, propValue) => ComplexEntity.ComplexEntityPropertyResolver(partitionKey, rowKey, propName, propValue) + }; + + List selectedColumns = new List(); + foreach (string key in sendEnt.Properties.Keys) + { + // Exclude null params since the service will ignore these on entity creation. + if (!key.Contains("Null")) + { + selectedColumns.Add(key); + } + } + + // Retrieve entity + TableResult result = currentTable.Execute(TableOperation.Retrieve(sendEnt.PartitionKey, sendEnt.RowKey, selectedColumns), options, null); + Assert.AreEqual(result.HttpStatusCode, (int)HttpStatusCode.OK); + DynamicTableEntity retrievedEntity = result.Result as DynamicTableEntity; + + // Validate entity + foreach (string key in selectedColumns) + { + Assert.AreEqual(sendEnt[key], retrievedEntity[key]); + } + } + + [TestMethod] + [Description("A test to check retrieve projection functionality selecting all valid columns.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableRetrieveWithFullProjectionWithEmptySelectSync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableRetrieveWithFullProjectionWithEmptySelectSync(payloadFormat); + } + } + + private void DoTableRetrieveWithFullProjectionWithEmptySelectSync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert entity + DynamicTableEntity sendEnt = new DynamicTableEntity(); + sendEnt.PartitionKey = Guid.NewGuid().ToString(); + sendEnt.RowKey = Guid.NewGuid().ToString(); + sendEnt.Properties = new ComplexEntity().WriteEntity(null); + currentTable.Execute(TableOperation.Insert(sendEnt)); + + TableRequestOptions options = new TableRequestOptions() + { + PropertyResolver = (partitionKey, rowKey, propName, propValue) => ComplexEntity.ComplexEntityPropertyResolver(partitionKey, rowKey, propName, propValue) + }; + + List selectedColumns = new List(); + + // Retrieve entity + TableResult result = currentTable.Execute(TableOperation.Retrieve(sendEnt.PartitionKey, sendEnt.RowKey, selectedColumns), options, null); + Assert.AreEqual(result.HttpStatusCode, (int)HttpStatusCode.OK); + DynamicTableEntity retrievedEntity = result.Result as DynamicTableEntity; + + // Validate entity + foreach (string key in retrievedEntity.Properties.Keys) + { + Assert.AreEqual(sendEnt[key], retrievedEntity[key]); + } + } + + [TestMethod] + [Description("A test to check retrieve projection functionality selecting only a subset of the columns.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableRetrieveWithPartialProjectionSync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableRetrieveWithPartialProjectionSync(payloadFormat); + } + } + + private void DoTableRetrieveWithPartialProjectionSync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert entity + DynamicTableEntity sendEnt = new DynamicTableEntity(); + sendEnt.PartitionKey = Guid.NewGuid().ToString(); + sendEnt.RowKey = Guid.NewGuid().ToString(); + sendEnt.Properties = new ComplexEntity().WriteEntity(null); + currentTable.Execute(TableOperation.Insert(sendEnt)); + + TableRequestOptions options = new TableRequestOptions() + { + PropertyResolver = (partitionKey, rowKey, propName, propValue) => ComplexEntity.ComplexEntityPropertyResolver(partitionKey, rowKey, propName, propValue) + }; + + List selectedColumns = new List + { + "Double", + "IntegerPrimitive", + "BoolPrimitive" + }; + + // Retrieve entity + TableResult result = currentTable.Execute(TableOperation.Retrieve(sendEnt.PartitionKey, sendEnt.RowKey, selectedColumns), options, null); + Assert.AreEqual(result.HttpStatusCode, (int)HttpStatusCode.OK); + DynamicTableEntity retrievedEntity = result.Result as DynamicTableEntity; + + // Validate entity + foreach (string key in sendEnt.Properties.Keys) + { + if (selectedColumns.Contains(key)) + { + Assert.AreEqual(sendEnt.Properties[key], retrievedEntity.Properties[key]); + } + else + { + Assert.IsFalse(retrievedEntity.Properties.ContainsKey(key)); + } + } + } + + [TestMethod] + [Description("A test to check retrieve projection functionality edge cases by comparing query results with retrieve results.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableRetrieveWithProjectionEdgeCasesSync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableRetrieveWithProjectionEdgeCasesSync(payloadFormat); + } + } + + private void DoTableRetrieveWithProjectionEdgeCasesSync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert entity + DynamicTableEntity sendEnt = new DynamicTableEntity(); + sendEnt.PartitionKey = Guid.NewGuid().ToString(); + sendEnt.RowKey = Guid.NewGuid().ToString(); + sendEnt.Properties = new ComplexEntity().WriteEntity(null); + currentTable.Execute(TableOperation.Insert(sendEnt)); + + TableRequestOptions options = new TableRequestOptions() + { + PropertyResolver = (partitionKey, rowKey, propName, propValue) => ComplexEntity.ComplexEntityPropertyResolver(partitionKey, rowKey, propName, propValue) + }; + + List selectedColumns = new List + { + "BoolPrimitive", + "InvalidProperty" + }; + + // Query entity + TableQuery query = new TableQuery().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sendEnt.PartitionKey)).Select(selectedColumns); + DynamicTableEntity retrievedEntityByQuery = currentTable.ExecuteQuerySegmented(query, null, options).Results[0] as DynamicTableEntity; + + // Retrieve entity + TableResult result = currentTable.Execute(TableOperation.Retrieve(sendEnt.PartitionKey, sendEnt.RowKey, selectedColumns), options, null); + DynamicTableEntity retrievedEntityByRetrieve = result.Result as DynamicTableEntity; + + // Validate entity + Assert.AreEqual(retrievedEntityByQuery.Properties["BoolPrimitive"], retrievedEntityByRetrieve.Properties["BoolPrimitive"]); + Assert.AreEqual(retrievedEntityByQuery.Properties["InvalidProperty"], retrievedEntityByRetrieve.Properties["InvalidProperty"]); + Assert.AreEqual(retrievedEntityByRetrieve.Properties["BoolPrimitive"].PropertyType, EdmType.Boolean); + Assert.AreEqual(retrievedEntityByRetrieve.Properties["InvalidProperty"].PropertyType, EdmType.String); + Assert.AreEqual(retrievedEntityByRetrieve.Properties["InvalidProperty"].StringValue, null); + } + + [TestMethod] + [Description("A test to check retrieve projection functionality with the overload of type TableResult.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableRetrieveWithTableResultOverloadWithProjectionSync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableRetrieveWithTableResultOverloadWithProjectionSync(payloadFormat); + } + } + + private void DoTableRetrieveWithTableResultOverloadWithProjectionSync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert entity + DynamicTableEntity sendEnt = new DynamicTableEntity(); + sendEnt.PartitionKey = Guid.NewGuid().ToString(); + sendEnt.RowKey = Guid.NewGuid().ToString(); + sendEnt.Properties = new ComplexEntity().WriteEntity(null); + sendEnt.Properties.Add("foo", new EntityProperty("bar")); + currentTable.Execute(TableOperation.Insert(sendEnt)); + + EntityResolver resolver = (pk, rk, ts, props, etag) => pk + rk + props["foo"].StringValue + props.Count; + + List selectedColumns = new List(); + foreach (string key in sendEnt.Properties.Keys) + { + // Exclude null params since the service will ignore these on entity creation. + if (!key.Contains("Null")) + { + selectedColumns.Add(key); + } + } + + // Retrieve entity + TableResult result = currentTable.Execute(TableOperation.Retrieve(sendEnt.PartitionKey, sendEnt.RowKey, resolver, selectedColumns)); + Assert.AreEqual(result.HttpStatusCode, (int)HttpStatusCode.OK); + + // Since there are properties in ComplexEntity set to null, we do not receive those from the server. Hence we need to check for non null values. + Assert.AreEqual((string)result.Result, sendEnt.PartitionKey + sendEnt.RowKey + sendEnt["foo"].StringValue + ComplexEntity.NumberOfNonNullProperties); + } + + [TestMethod] + [Description("A test to check retrieve projection functionality with the generic overload.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void TableRetrieveWithGenericOverloadWithProjectionSync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + DoTableRetrieveWithGenericOverloadWithProjectionSync(payloadFormat); + } + } + + private void DoTableRetrieveWithGenericOverloadWithProjectionSync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert entity + string pk = Guid.NewGuid().ToString(); + string rk = Guid.NewGuid().ToString(); + TableEntity.DisableCompiledSerializers = true; + ComplexEntity sendEnt = new ComplexEntity(pk, rk); + sendEnt.String = "ResetTestTotested"; + sendEnt.Double = (Double)5678.5678; + sendEnt.IntegerPrimitive = 5678; + currentTable.Execute(TableOperation.Insert(sendEnt)); + + List selectedColumns = new List + { + "Double", + "IntegerPrimitive" + }; + + // Retrieve entity + TableResult result = currentTable.Execute(TableOperation.Retrieve(sendEnt.PartitionKey, sendEnt.RowKey, selectedColumns)); + ComplexEntity retEntity = result.Result as ComplexEntity; + + // Validate entity + Assert.AreEqual(retEntity.Double, sendEnt.Double); + Assert.AreEqual(retEntity.IntegerPrimitive, sendEnt.IntegerPrimitive); + Assert.AreNotEqual(retEntity.String, sendEnt.String); + } + [TestMethod] [Description("A test to check retrieve functionality Sync")] [TestCategory(ComponentCategory.Table)] diff --git a/Test/Common/Table/Entities/ComplexEntity.cs b/Test/Common/Table/Entities/ComplexEntity.cs index 7aa16e96c..a5e30d752 100644 --- a/Test/Common/Table/Entities/ComplexEntity.cs +++ b/Test/Common/Table/Entities/ComplexEntity.cs @@ -382,6 +382,7 @@ public static EdmType ComplexEntityPropertyResolver(string pk, string rk, string return EdmType.Guid; case "Double": + case "DoubleInteger": case "DoubleN": case "DoubleNull": case "DoublePrimitive": diff --git a/Test/WindowsDesktop/DictionaryKeyResolver.cs b/Test/WindowsDesktop/DictionaryKeyResolver.cs index 74657533a..2e449b367 100644 --- a/Test/WindowsDesktop/DictionaryKeyResolver.cs +++ b/Test/WindowsDesktop/DictionaryKeyResolver.cs @@ -31,11 +31,11 @@ public void Add(IKey key) keys[key.Kid] = key; } - public async Task ResolveKeyAsync(string kid, CancellationToken token) + public Task ResolveKeyAsync(string kid, CancellationToken token) { IKey result; keys.TryGetValue(kid, out result); - return result; + return new Task(() => result); } } } diff --git a/Test/WindowsDesktop/Properties/AssemblyInfo.cs b/Test/WindowsDesktop/Properties/AssemblyInfo.cs index 6deb68710..b9d5b02fb 100644 --- a/Test/WindowsDesktop/Properties/AssemblyInfo.cs +++ b/Test/WindowsDesktop/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] diff --git a/Test/WindowsPhone/Properties/AssemblyInfo.cs b/Test/WindowsPhone/Properties/AssemblyInfo.cs index 94a0b5ffd..632a9a046 100644 --- a/Test/WindowsPhone/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone/Properties/AssemblyInfo.cs @@ -32,6 +32,6 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhone81/Properties/AssemblyInfo.cs b/Test/WindowsPhone81/Properties/AssemblyInfo.cs index 47db80e37..c2c0bf946 100644 --- a/Test/WindowsPhone81/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhone81/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs index 66e54e206..ad5217e80 100644 --- a/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs +++ b/Test/WindowsPhoneRT.Test/Properties/AssemblyInfo.cs @@ -24,6 +24,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] [assembly: ComVisible(false)] \ No newline at end of file diff --git a/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs b/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs index 187a66f62..ee140e7c7 100644 --- a/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs +++ b/Test/WindowsRuntime/File/CloudFileDirectoryTest.cs @@ -153,6 +153,60 @@ public async Task CloudFileDirectoryCreateIfNotExistsAsync() } } + [TestMethod] + [Description("Verify that a file directory's metadata can be updated")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileDirectorySetMetadataAsync() + { + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + + CloudFileDirectory directory = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + await directory.CreateAsync(); + + CloudFileDirectory directory2 = share.GetRootDirectoryReference().GetDirectoryReference("directory1"); + await directory2.FetchAttributesAsync(); + Assert.AreEqual(0, directory2.Metadata.Count); + + directory.Metadata["key1"] = null; + OperationContext context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await directory.SetMetadataAsync(null /* accessConditions */, null /* options */, context), + context, + "Metadata keys should have a non-null value", + HttpStatusCode.Unused); + + directory.Metadata["key1"] = ""; + await TestHelper.ExpectedExceptionAsync( + async () => await directory.SetMetadataAsync(null /* accessConditions */, null /* options */, context), + context, + "Metadata keys should have a non-empty value", + HttpStatusCode.Unused); + + directory.Metadata["key1"] = "value1"; + await directory.SetMetadataAsync(null /* accessConditions */, null /* options */, context); + + await directory2.FetchAttributesAsync(); + Assert.AreEqual(1, directory2.Metadata.Count); + Assert.AreEqual("value1", directory2.Metadata["key1"]); + + directory.Metadata.Clear(); + await directory.SetMetadataAsync(null /* accessConditions */, null /* options */, context); + + await directory2.FetchAttributesAsync(); + Assert.AreEqual(0, directory2.Metadata.Count); + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + [TestMethod] [Description("Try to delete a non-existing directory")] [TestCategory(ComponentCategory.File)] diff --git a/Test/WindowsRuntime/File/CloudFileServiceTest.cs b/Test/WindowsRuntime/File/CloudFileServiceTest.cs new file mode 100644 index 000000000..37d5868c7 --- /dev/null +++ b/Test/WindowsRuntime/File/CloudFileServiceTest.cs @@ -0,0 +1,133 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +namespace Microsoft.WindowsAzure.Storage.File +{ + using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; + using Microsoft.WindowsAzure.Storage.Core.Util; + using Microsoft.WindowsAzure.Storage.File.Protocol; + using Microsoft.WindowsAzure.Storage.Shared.Protocol; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Threading.Tasks; + + [TestClass] + public class CloudFileServiceTest : FileTestBase +#if XUNIT +, IDisposable +#endif + { + +#if XUNIT + // Todo: The simple/nonefficient workaround is to minimize change and support Xunit, + // removed when we support mstest on projectK + public CloudFileServiceTest() + { + MyTestInitialize(); + } + public void Dispose() + { + MyTestCleanup(); + } +#endif + // + // Use TestInitialize to run code before running each test + [TestInitialize()] + public void MyTestInitialize() + { + if (TestBase.FileBufferManager != null) + { + TestBase.FileBufferManager.OutstandingBufferCount = 0; + } + } + // + // Use TestCleanup to run code after each test has run + [TestCleanup()] + public void MyTestCleanup() + { + if (TestBase.FileBufferManager != null) + { + Assert.AreEqual(0, TestBase.FileBufferManager.OutstandingBufferCount); + } + } + + [TestMethod] + [Description("Test Set/Get Service Properties Async")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileTestAnalyticsRoundTripAsync() + { + FileServiceProperties props = DefaultServiceProperties(); + props.Cors.CorsRules.Add( + new CorsRule() + { + AllowedOrigins = new List() { "www.ab.com", "www.bc.com" }, + AllowedMethods = CorsHttpMethods.Get | CorsHttpMethods.Put, + + MaxAgeInSeconds = 500, + ExposedHeaders = + new List() + { + "x-ms-meta-data*", + "x-ms-meta-source*", + "x-ms-meta-abc", + "x-ms-meta-bcd" + }, + AllowedHeaders = + new List() + { + "x-ms-meta-data*", + "x-ms-meta-target*", + "x-ms-meta-xyz", + "x-ms-meta-foo" + } + }); + + props.MinuteMetrics.MetricsLevel = MetricsLevel.Service; + props.MinuteMetrics.RetentionDays = 8; + + props.HourMetrics.MetricsLevel = MetricsLevel.None; + props.HourMetrics.RetentionDays = 1; + + CloudFileClient client = GenerateCloudFileClient(); + await client.SetServicePropertiesAsync(props); + + TestHelper.AssertFileServicePropertiesAreEqual(props, await client.GetServicePropertiesAsync()); + } + + private static FileServiceProperties DefaultServiceProperties() + { + FileServiceProperties props = new FileServiceProperties(); + + props.HourMetrics.MetricsLevel = MetricsLevel.None; + props.HourMetrics.RetentionDays = null; + props.HourMetrics.Version = "1.0"; + + props.MinuteMetrics.MetricsLevel = MetricsLevel.None; + props.MinuteMetrics.RetentionDays = null; + props.MinuteMetrics.Version = "1.0"; + + props.Cors.CorsRules = new List(); + + return props; + } + } +} \ No newline at end of file diff --git a/Test/WindowsRuntime/File/CloudFileShareTest.cs b/Test/WindowsRuntime/File/CloudFileShareTest.cs index 44f3d3dca..eebc68c55 100644 --- a/Test/WindowsRuntime/File/CloudFileShareTest.cs +++ b/Test/WindowsRuntime/File/CloudFileShareTest.cs @@ -348,6 +348,476 @@ public async Task CloudFileShareListFilesAndDirectoriesSegmentedAsync() } } + [TestMethod] + [Description("Set share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileShareSetPermissionsAsync() + { + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + + FileSharePermissions permissions = await share.GetPermissionsAsync(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + permissions.SharedAccessPolicies.Add("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + await share.SetPermissionsAsync(permissions); + await Task.Delay(30 * 1000); + + CloudFileShare share2 = share.ServiceClient.GetShareReference(share.Name); + permissions = await share2.GetPermissionsAsync(); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.HasValue); + Assert.AreEqual(start, permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.Value.UtcDateTime); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.HasValue); + Assert.AreEqual(expiry, permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.Value.UtcDateTime); + Assert.AreEqual(SharedAccessFilePermissions.List, permissions.SharedAccessPolicies["key1"].Permissions); + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Set share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileShareSetPermissionsOverloadAsync() + { + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + + FileSharePermissions permissions = await share.GetPermissionsAsync(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + await share.SetPermissionsAsync(permissions); + await Task.Delay(30 * 1000); + + CloudFileShare share2 = share.ServiceClient.GetShareReference(share.Name); + permissions = await share2.GetPermissionsAsync(); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.HasValue); + Assert.AreEqual(start, permissions.SharedAccessPolicies["key1"].SharedAccessStartTime.Value.UtcDateTime); + Assert.IsTrue(permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.HasValue); + Assert.AreEqual(expiry, permissions.SharedAccessPolicies["key1"].SharedAccessExpiryTime.Value.UtcDateTime); + Assert.AreEqual(SharedAccessFilePermissions.List, permissions.SharedAccessPolicies["key1"].Permissions); + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Clear share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileShareClearPermissionsAsync() + { + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + + FileSharePermissions permissions = await share.GetPermissionsAsync(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + await share.SetPermissionsAsync(permissions); + await Task.Delay(3 * 1000); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + + Assert.AreEqual(true, permissions.SharedAccessPolicies.Contains(sharedAccessPolicy)); + Assert.AreEqual(true, permissions.SharedAccessPolicies.ContainsKey("key1")); + permissions.SharedAccessPolicies.Clear(); + await share.SetPermissionsAsync(permissions); + await Task.Delay(3 * 1000); + permissions = await share.GetPermissionsAsync(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Copy share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileShareCopyPermissionsAsync() + { + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + + FileSharePermissions permissions = await share.GetPermissionsAsync(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + DateTime start2 = DateTime.UtcNow; + start2 = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry2 = start.AddMinutes(30); + KeyValuePair sharedAccessPolicy2 = new KeyValuePair("key2", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start2, + SharedAccessExpiryTime = expiry2, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy2); + + KeyValuePair[] sharedAccessPolicyArray = new KeyValuePair[2]; + permissions.SharedAccessPolicies.CopyTo(sharedAccessPolicyArray, 0); + Assert.AreEqual(2, sharedAccessPolicyArray.Length); + Assert.AreEqual(sharedAccessPolicy, sharedAccessPolicyArray[0]); + Assert.AreEqual(sharedAccessPolicy2, sharedAccessPolicyArray[1]); + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Remove share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileShareRemovePermissionsAsync() + { + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + + FileSharePermissions permissions = await share.GetPermissionsAsync(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + DateTime start2 = DateTime.UtcNow; + start2 = new DateTime(start2.Year, start2.Month, start2.Day, start2.Hour, start2.Minute, start2.Second, DateTimeKind.Utc); + DateTime expiry2 = start2.AddMinutes(30); + KeyValuePair sharedAccessPolicy2 = new KeyValuePair("key2", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start2, + SharedAccessExpiryTime = expiry2, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy2); + await share.SetPermissionsAsync(permissions); + Assert.AreEqual(2, permissions.SharedAccessPolicies.Count); + + permissions.SharedAccessPolicies.Remove(sharedAccessPolicy2); + await share.SetPermissionsAsync(permissions); + await Task.Delay(3 * 1000); + + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + permissions = await share.GetPermissionsAsync(); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + Assert.AreEqual(sharedAccessPolicy.Key, permissions.SharedAccessPolicies.ElementAt(0).Key); + Assert.AreEqual(sharedAccessPolicy.Value.Permissions, permissions.SharedAccessPolicies.ElementAt(0).Value.Permissions); + Assert.AreEqual(sharedAccessPolicy.Value.SharedAccessStartTime, permissions.SharedAccessPolicies.ElementAt(0).Value.SharedAccessStartTime); + Assert.AreEqual(sharedAccessPolicy.Value.SharedAccessExpiryTime, permissions.SharedAccessPolicies.ElementAt(0).Value.SharedAccessExpiryTime); + + permissions.SharedAccessPolicies.Add(sharedAccessPolicy2); + await share.SetPermissionsAsync(permissions); + Assert.AreEqual(2, permissions.SharedAccessPolicies.Count); + + permissions.SharedAccessPolicies.Remove("key2"); + await share.SetPermissionsAsync(permissions); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + permissions = await share.GetPermissionsAsync(); + Assert.AreEqual(1, permissions.SharedAccessPolicies.Count); + Assert.AreEqual(sharedAccessPolicy.Key, permissions.SharedAccessPolicies.ElementAt(0).Key); + Assert.AreEqual(sharedAccessPolicy.Value.Permissions, permissions.SharedAccessPolicies.ElementAt(0).Value.Permissions); + Assert.AreEqual(sharedAccessPolicy.Value.SharedAccessStartTime, permissions.SharedAccessPolicies.ElementAt(0).Value.SharedAccessStartTime); + Assert.AreEqual(sharedAccessPolicy.Value.SharedAccessExpiryTime, permissions.SharedAccessPolicies.ElementAt(0).Value.SharedAccessExpiryTime); + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("TryGetValue for share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileShareTryGetValuePermissions() + { + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + + FileSharePermissions permissions = await share.GetPermissionsAsync(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + DateTime start2 = DateTime.UtcNow; + start2 = new DateTime(start2.Year, start2.Month, start2.Day, start2.Hour, start2.Minute, start2.Second, DateTimeKind.Utc); + DateTime expiry2 = start2.AddMinutes(30); + KeyValuePair sharedAccessPolicy2 = new KeyValuePair("key2", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start2, + SharedAccessExpiryTime = expiry2, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy2); + await share.SetPermissionsAsync(permissions); + await Task.Delay(3 * 1000); + Assert.AreEqual(2, permissions.SharedAccessPolicies.Count); + + permissions = await share.GetPermissionsAsync(); + SharedAccessFilePolicy retrPolicy; + permissions.SharedAccessPolicies.TryGetValue("key1", out retrPolicy); + Assert.AreEqual(sharedAccessPolicy.Value.Permissions, retrPolicy.Permissions); + Assert.AreEqual(sharedAccessPolicy.Value.SharedAccessStartTime, retrPolicy.SharedAccessStartTime); + Assert.AreEqual(sharedAccessPolicy.Value.SharedAccessExpiryTime, retrPolicy.SharedAccessExpiryTime); + + SharedAccessFilePolicy retrPolicy2; + permissions.SharedAccessPolicies.TryGetValue("key2", out retrPolicy2); + Assert.AreEqual(sharedAccessPolicy2.Value.Permissions, retrPolicy2.Permissions); + Assert.AreEqual(sharedAccessPolicy2.Value.SharedAccessStartTime, retrPolicy2.SharedAccessStartTime); + Assert.AreEqual(sharedAccessPolicy2.Value.SharedAccessExpiryTime, retrPolicy2.SharedAccessExpiryTime); + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("GetEnumerator for share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileShareGetEnumeratorPermissionsAsync() + { + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + + FileSharePermissions permissions = await share.GetPermissionsAsync(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + DateTime start2 = DateTime.UtcNow; + start2 = new DateTime(start2.Year, start2.Month, start2.Day, start2.Hour, start2.Minute, start2.Second, DateTimeKind.Utc); + DateTime expiry2 = start2.AddMinutes(30); + KeyValuePair sharedAccessPolicy2 = new KeyValuePair("key2", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start2, + SharedAccessExpiryTime = expiry2, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy2); + Assert.AreEqual(2, permissions.SharedAccessPolicies.Count); + + IEnumerator> policies = permissions.SharedAccessPolicies.GetEnumerator(); + policies.MoveNext(); + Assert.AreEqual(sharedAccessPolicy, policies.Current); + policies.MoveNext(); + Assert.AreEqual(sharedAccessPolicy2, policies.Current); + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("GetValues for share permissions")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileShareGetValuesPermissionsAsync() + { + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + + FileSharePermissions permissions = await share.GetPermissionsAsync(); + Assert.AreEqual(0, permissions.SharedAccessPolicies.Count); + + // We do not have precision at milliseconds level. Hence, we need + // to recreate the start DateTime to be able to compare it later. + DateTime start = DateTime.UtcNow; + start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, DateTimeKind.Utc); + DateTime expiry = start.AddMinutes(30); + + KeyValuePair sharedAccessPolicy = new KeyValuePair("key1", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start, + SharedAccessExpiryTime = expiry, + Permissions = SharedAccessFilePermissions.List, + }); + + DateTime start2 = DateTime.UtcNow; + start2 = new DateTime(start2.Year, start2.Month, start2.Day, start2.Hour, start2.Minute, start2.Second, DateTimeKind.Utc); + DateTime expiry2 = start2.AddMinutes(30); + KeyValuePair sharedAccessPolicy2 = new KeyValuePair("key2", new SharedAccessFilePolicy() + { + SharedAccessStartTime = start2, + SharedAccessExpiryTime = expiry2, + Permissions = SharedAccessFilePermissions.List, + }); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy); + permissions.SharedAccessPolicies.Add(sharedAccessPolicy2); + Assert.AreEqual(2, permissions.SharedAccessPolicies.Count); + + ICollection values = permissions.SharedAccessPolicies.Values; + Assert.AreEqual(2, values.Count); + Assert.AreEqual(sharedAccessPolicy.Value, values.ElementAt(0)); + Assert.AreEqual(sharedAccessPolicy2.Value, values.ElementAt(1)); + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Get permissions from string")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public void CloudFileSharePermissionsFromStringAsync() + { + SharedAccessFilePolicy policy = new SharedAccessFilePolicy(); + policy.SharedAccessStartTime = DateTime.UtcNow; + policy.SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(30); + + policy.Permissions = SharedAccessFilePolicy.PermissionsFromString("rwdl"); + Assert.AreEqual(SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Write | SharedAccessFilePermissions.Delete | SharedAccessFilePermissions.List, policy.Permissions); + + policy.Permissions = SharedAccessFilePolicy.PermissionsFromString("rwl"); + Assert.AreEqual(SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Write | SharedAccessFilePermissions.List, policy.Permissions); + + policy.Permissions = SharedAccessFilePolicy.PermissionsFromString("rw"); + Assert.AreEqual(SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Write, policy.Permissions); + + policy.Permissions = SharedAccessFilePolicy.PermissionsFromString("rd"); + Assert.AreEqual(SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Delete, policy.Permissions); + + policy.Permissions = SharedAccessFilePolicy.PermissionsFromString("wl"); + Assert.AreEqual(SharedAccessFilePermissions.Write | SharedAccessFilePermissions.List, policy.Permissions); + + policy.Permissions = SharedAccessFilePolicy.PermissionsFromString("w"); + Assert.AreEqual(SharedAccessFilePermissions.Write, policy.Permissions); + } + /* [TestMethod] [Description("Test conditional access on a share")] diff --git a/Test/WindowsRuntime/File/CopyFileTest.cs b/Test/WindowsRuntime/File/CopyFileTest.cs new file mode 100644 index 000000000..4778ad55a --- /dev/null +++ b/Test/WindowsRuntime/File/CopyFileTest.cs @@ -0,0 +1,325 @@ +// ----------------------------------------------------------------------------------------- +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using Microsoft.WindowsAzure.Storage.Core.Util; +using Microsoft.WindowsAzure.Storage.Auth; +using Microsoft.WindowsAzure.Storage.Blob; +using System; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.WindowsAzure.Storage.File +{ + [TestClass] + public class CopyFileTest : FileTestBase +#if XUNIT +, IDisposable +#endif + { + +#if XUNIT + // Todo: The simple/nonefficient workaround is to minimize change and support Xunit, + // removed when we support mstest on projectK + public CopyFileTest() + { + MyTestInitialize(); + } + public void Dispose() + { + MyTestCleanup(); + } +#endif + // + // Use TestInitialize to run code before running each test + [TestInitialize()] + public void MyTestInitialize() + { + if (TestBase.FileBufferManager != null) + { + TestBase.FileBufferManager.OutstandingBufferCount = 0; + } + } + // + // Use TestCleanup to run code after each test has run + [TestCleanup()] + public void MyTestCleanup() + { + if (TestBase.FileBufferManager != null) + { + Assert.AreEqual(0, TestBase.FileBufferManager.OutstandingBufferCount); + } + } + + [TestMethod] + [Description("CopyFromFile with Unicode source file")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CopyFileUsingUnicodeFileNameAsync() + { + string _unicodeFileName = "繁体字14a6c"; + string _nonUnicodeFileName = "sample_file"; + + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + CloudFile fileUnicodeSource = share.GetRootDirectoryReference().GetFileReference(_unicodeFileName); + string data = "Test content"; + await UploadTextAsync(fileUnicodeSource, data, Encoding.UTF8); + CloudFile fileAsciiSource = share.GetRootDirectoryReference().GetFileReference(_nonUnicodeFileName); + await UploadTextAsync(fileAsciiSource, data, Encoding.UTF8); + + //Copy files over + CloudFile fileAsciiDest = share.GetRootDirectoryReference().GetFileReference(_nonUnicodeFileName + "_copy"); + string asciiCopyId = await fileAsciiDest.StartCopyAsync(TestHelper.Defiddler(fileAsciiSource)); + await WaitForCopyAsync(fileAsciiDest); + + CloudFile fileUnicodeDest = share.GetRootDirectoryReference().GetFileReference(_unicodeFileName + "_copy"); + string unicodeCopyId = await fileUnicodeDest.StartCopyAsync(TestHelper.Defiddler(fileUnicodeSource)); + await WaitForCopyAsync(fileUnicodeDest); + + // Check ascii copy + Assert.AreEqual(CopyStatus.Success, fileAsciiDest.CopyState.Status); + Assert.AreEqual(fileAsciiSource.Uri.AbsolutePath, fileAsciiDest.CopyState.Source.AbsolutePath); + Assert.AreEqual(data.Length, fileAsciiDest.CopyState.TotalBytes); + Assert.AreEqual(data.Length, fileAsciiDest.CopyState.BytesCopied); + Assert.AreEqual(asciiCopyId, fileAsciiDest.CopyState.CopyId); + Assert.IsTrue(fileAsciiDest.CopyState.CompletionTime > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + + // Check unicode copy + Assert.AreEqual(CopyStatus.Success, fileUnicodeDest.CopyState.Status); + Assert.AreEqual(fileUnicodeSource.Uri.AbsolutePath, fileUnicodeDest.CopyState.Source.AbsolutePath); + Assert.AreEqual(data.Length, fileUnicodeDest.CopyState.TotalBytes); + Assert.AreEqual(data.Length, fileUnicodeDest.CopyState.BytesCopied); + Assert.AreEqual(unicodeCopyId, fileUnicodeDest.CopyState.CopyId); + Assert.IsTrue(fileUnicodeDest.CopyState.CompletionTime > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + private static async Task CloudFileCopyAsync(bool sourceIsSas, bool destinationIsSas) + { + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + + // Create Source on server + CloudFile source = share.GetRootDirectoryReference().GetFileReference("source"); + + string data = "String data"; + await UploadTextAsync(source, data, Encoding.UTF8); + + source.Metadata["Test"] = "value"; + await source.SetMetadataAsync(); + + // Create Destination on server + CloudFile destination = share.GetRootDirectoryReference().GetFileReference("destination"); + await destination.CreateAsync(1); + + CloudFile copySource = source; + CloudFile copyDestination = destination; + + if (sourceIsSas) + { + // Source SAS must have read permissions + SharedAccessFilePermissions permissions = SharedAccessFilePermissions.Read; + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = permissions, + }; + string sasToken = source.GetSharedAccessSignature(policy); + + // Get source + StorageCredentials credentials = new StorageCredentials(sasToken); + copySource = new CloudFile(credentials.TransformUri(source.Uri)); + } + + if (destinationIsSas) + { + Assert.IsTrue(sourceIsSas); + + // Destination SAS must have write permissions + SharedAccessFilePermissions permissions = SharedAccessFilePermissions.Write; + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = permissions, + }; + string sasToken = destination.GetSharedAccessSignature(policy); + + // Get destination + StorageCredentials credentials = new StorageCredentials(sasToken); + copyDestination = new CloudFile(credentials.TransformUri(destination.Uri)); + } + + // Start copy and wait for completion + string copyId = await copyDestination.StartCopyAsync(TestHelper.Defiddler(copySource)); + await WaitForCopyAsync(destination); + + // Check original file references for equality + Assert.AreEqual(CopyStatus.Success, destination.CopyState.Status); + Assert.AreEqual(source.Uri.AbsolutePath, destination.CopyState.Source.AbsolutePath); + Assert.AreEqual(data.Length, destination.CopyState.TotalBytes); + Assert.AreEqual(data.Length, destination.CopyState.BytesCopied); + Assert.AreEqual(copyId, destination.CopyState.CopyId); + Assert.IsTrue(destination.CopyState.CompletionTime > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + + if (!destinationIsSas) + { + // Abort Copy is not supported for SAS destination + OperationContext context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await copyDestination.AbortCopyAsync(copyId, null, null, context), + context, + "Aborting a copy operation after completion should fail", + HttpStatusCode.Conflict, + "NoPendingCopyOperation"); + } + + await source.FetchAttributesAsync(); + Assert.IsNotNull(destination.Properties.ETag); + Assert.AreNotEqual(source.Properties.ETag, destination.Properties.ETag); + Assert.IsTrue(destination.Properties.LastModified > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + + string copyData = await DownloadTextAsync(destination, Encoding.UTF8); + Assert.AreEqual(data, copyData, "Data inside copy of file not equal."); + + await destination.FetchAttributesAsync(); + FileProperties prop1 = destination.Properties; + FileProperties prop2 = source.Properties; + + Assert.AreEqual(prop1.CacheControl, prop2.CacheControl); + Assert.AreEqual(prop1.ContentEncoding, prop2.ContentEncoding); + Assert.AreEqual(prop1.ContentLanguage, prop2.ContentLanguage); + Assert.AreEqual(prop1.ContentMD5, prop2.ContentMD5); + Assert.AreEqual(prop1.ContentType, prop2.ContentType); + + Assert.AreEqual("value", destination.Metadata["Test"], false, "Copied metadata not same"); + + await destination.DeleteAsync(); + await source.DeleteAsync(); + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + [TestMethod] + [Description("Copy a file and then verify its contents, properties, and metadata")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileCopySasToSasTestAsync() + { + await CloudFileCopyAsync(true, true); + } + + [TestMethod] + [Description("Copy a file and then verify its contents, properties, and metadata")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileCopyFromSasTestAsync() + { + await CloudFileCopyAsync(true, false); + } + + [TestMethod] + [Description("Copy a file and then verify its contents, properties, and metadata")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileCopyTestAsync() + { + await CloudFileCopyAsync(false, false); + } + + [TestMethod] + [Description("Copy a file and override metadata during copy")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileCopyTestWithMetadataOverrideAsync() + { + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + + CloudFile source = share.GetRootDirectoryReference().GetFileReference("source"); + + string data = "String data"; + await UploadTextAsync(source, data, Encoding.UTF8); + + source.Metadata["Test"] = "value"; + await source.SetMetadataAsync(); + + CloudFile copy = share.GetRootDirectoryReference().GetFileReference("copy"); + copy.Metadata["Test2"] = "value2"; + string copyId = await copy.StartCopyAsync(TestHelper.Defiddler(source)); + await WaitForCopyAsync(copy); + Assert.AreEqual(CopyStatus.Success, copy.CopyState.Status); + Assert.AreEqual(source.Uri.AbsolutePath, copy.CopyState.Source.AbsolutePath); + Assert.AreEqual(data.Length, copy.CopyState.TotalBytes); + Assert.AreEqual(data.Length, copy.CopyState.BytesCopied); + Assert.AreEqual(copyId, copy.CopyState.CopyId); + Assert.IsTrue(copy.CopyState.CompletionTime > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(1))); + + string copyData = await DownloadTextAsync(copy, Encoding.UTF8); + Assert.AreEqual(data, copyData, "Data inside copy of file not similar"); + + await copy.FetchAttributesAsync(); + await source.FetchAttributesAsync(); + FileProperties prop1 = copy.Properties; + FileProperties prop2 = source.Properties; + + Assert.AreEqual(prop1.CacheControl, prop2.CacheControl); + Assert.AreEqual(prop1.ContentEncoding, prop2.ContentEncoding); + Assert.AreEqual(prop1.ContentDisposition, prop2.ContentDisposition); + Assert.AreEqual(prop1.ContentLanguage, prop2.ContentLanguage); + Assert.AreEqual(prop1.ContentMD5, prop2.ContentMD5); + Assert.AreEqual(prop1.ContentType, prop2.ContentType); + + Assert.AreEqual("value2", copy.Metadata["Test2"], false, "Copied metadata not same"); + Assert.IsFalse(copy.Metadata.ContainsKey("Test"), "Source Metadata should not appear in destination file"); + + await copy.DeleteAsync(); + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + } +} diff --git a/Test/WindowsRuntime/File/FileSASTests.cs b/Test/WindowsRuntime/File/FileSASTests.cs new file mode 100644 index 000000000..d4c4e33bd --- /dev/null +++ b/Test/WindowsRuntime/File/FileSASTests.cs @@ -0,0 +1,493 @@ +// +// Copyright 2013 Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------------------- + +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using Microsoft.WindowsAzure.Storage; +using Microsoft.WindowsAzure.Storage.Auth; +using Microsoft.WindowsAzure.Storage.Shared.Protocol; +using Microsoft.WindowsAzure.Storage.Core; +using System; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using System.IO; + +namespace Microsoft.WindowsAzure.Storage.File +{ + [TestClass] + public class FileSASTests : FileTestBase +#if XUNIT +, IDisposable +#endif + { + +#if XUNIT + // Todo: The simple/nonefficient workaround is to minimize change and support Xunit, + // removed when we support mstest on projectK + public FileSASTests() + { + MyTestInitialize(); + } + public void Dispose() + { + MyTestCleanup(); + } +#endif + private CloudFileShare testShare; + + [TestInitialize] + public void MyTestInitialize() + { + this.testShare = GetRandomShareReference(); + this.testShare.CreateAsync().AsTask().Wait(); + + if (TestBase.FileBufferManager != null) + { + TestBase.FileBufferManager.OutstandingBufferCount = 0; + } + } + + [TestCleanup] + public void MyTestCleanup() + { + this.testShare.DeleteAsync().AsTask().Wait(); + this.testShare = null; + if (TestBase.FileBufferManager != null) + { + Assert.AreEqual(0, TestBase.FileBufferManager.OutstandingBufferCount); + } + } + + private static async Task TestAccess(string sasToken, SharedAccessFilePermissions permissions, SharedAccessFileHeaders headers, CloudFileShare share, CloudFile file) + { + CloudFileShare SASshare = null; + CloudFile SASfile; + OperationContext context = new OperationContext(); + StorageCredentials credentials = string.IsNullOrEmpty(sasToken) ? + new StorageCredentials() : + new StorageCredentials(sasToken); + string fileText = "file"; + + if (share != null) + { + SASshare = new CloudFileShare(credentials.TransformUri(share.Uri)); + SASfile = SASshare.GetRootDirectoryReference().GetFileReference(file.Name); + } + else + { + SASfile = new CloudFile(credentials.TransformUri(file.Uri)); + } + + if ((permissions & SharedAccessFilePermissions.Write) == SharedAccessFilePermissions.Write) + { + await UploadTextAsync(SASfile, fileText, Encoding.UTF8); + } + else if ((permissions & SharedAccessFilePermissions.Create) == SharedAccessFilePermissions.Create) + { + await SASfile.CreateAsync(Encoding.UTF8.GetBytes(fileText).Length); + context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await UploadTextAsync(SASfile, fileText, Encoding.UTF8, operationContext:context), + context, + "UploadText SAS does not allow for writing", + HttpStatusCode.Forbidden, + ""); + await UploadTextAsync(file, fileText, Encoding.UTF8); + + } + else + { + context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await SASfile.CreateAsync(Encoding.UTF8.GetBytes(fileText).Length, null /* accessCondition */, null /* options */, context), + context, + "Create file succeeded but SAS does not allow for writing/creating", + HttpStatusCode.Forbidden, + ""); + context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await UploadTextAsync(SASfile, fileText, Encoding.UTF8, operationContext:context), + context, + "UploadText SAS does not allow for writing/creating", + HttpStatusCode.Forbidden, + ""); + await UploadTextAsync(file, fileText, Encoding.UTF8); + } + + if (SASshare != null) + { + if ((permissions & SharedAccessFilePermissions.List) == SharedAccessFilePermissions.List) + { + var results = await SASshare.GetRootDirectoryReference().ListFilesAndDirectoriesSegmentedAsync(null); + } + else + { + context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => { var results = await SASshare.GetRootDirectoryReference().ListFilesAndDirectoriesSegmentedAsync(null /* maxResults */, null /* currentToken */, null /* options */, context); }, + context, + "List files while SAS does not allow for listing", + HttpStatusCode.Forbidden, + ""); + } + } + + if ((permissions & SharedAccessFilePermissions.Read) == SharedAccessFilePermissions.Read) + { + await SASfile.FetchAttributesAsync(); + + // Test headers + if (headers != null) + { + if (headers.CacheControl != null) + { + Assert.AreEqual(headers.CacheControl, SASfile.Properties.CacheControl); + } + + if (headers.ContentDisposition != null) + { + Assert.AreEqual(headers.ContentDisposition, SASfile.Properties.ContentDisposition); + } + + if (headers.ContentEncoding != null) + { + Assert.AreEqual(headers.ContentEncoding, SASfile.Properties.ContentEncoding); + } + + if (headers.ContentLanguage != null) + { + Assert.AreEqual(headers.ContentLanguage, SASfile.Properties.ContentLanguage); + } + + if (headers.ContentType != null) + { + Assert.AreEqual(headers.ContentType, SASfile.Properties.ContentType); + } + } + } + else + { + context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await SASfile.FetchAttributesAsync(null /* accessCondition */, null /* options */, context), + context, + "Fetch file attributes while SAS does not allow for reading", + HttpStatusCode.Forbidden, + ""); + } + + if ((permissions & SharedAccessFilePermissions.Write) == SharedAccessFilePermissions.Write) + { + await SASfile.SetMetadataAsync(); + } + else + { + context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await SASfile.SetMetadataAsync(null /* accessCondition */, null /* options */, context), + context, + "Set file metadata while SAS does not allow for writing", + HttpStatusCode.Forbidden, + ""); + } + + if ((permissions & SharedAccessFilePermissions.Delete) == SharedAccessFilePermissions.Delete) + { + await SASfile.DeleteAsync(); + } + else + { + context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await SASfile.DeleteAsync(null /* accessCondition */, null /* options */, context), + context, + "Delete file while SAS does not allow for deleting", + HttpStatusCode.Forbidden, + ""); + } + } + + private static async Task TestFileSAS(CloudFile testFile, SharedAccessFilePermissions permissions, SharedAccessFileHeaders headers) + { + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = permissions, + }; + + string sasToken = testFile.GetSharedAccessSignature(policy, headers, null); + await TestAccess(sasToken, permissions, headers, null, testFile); + } + + [TestMethod] + [Description("Test updateSASToken")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileShareUpdateSASTokenAsync() + { + // Create a policy with read/write access and get SAS. + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Write, + }; + string sasToken = this.testShare.GetSharedAccessSignature(policy); + //Thread.Sleep(35000); + CloudFile testFile = this.testShare.GetRootDirectoryReference().GetFileReference("file"); + await UploadTextAsync(testFile, "file", Encoding.UTF8); + await TestAccess(sasToken, SharedAccessFilePermissions.Read | SharedAccessFilePermissions.Write, null, this.testShare, testFile); + + StorageCredentials creds = new StorageCredentials(sasToken); + + // Change the policy to only read and update SAS. + SharedAccessFilePolicy policy2 = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = SharedAccessFilePermissions.Read + }; + string sasToken2 = this.testShare.GetSharedAccessSignature(policy2); + creds.UpdateSASToken(sasToken2); + + // Extra check to make sure that we have actually updated the SAS token. + CloudFileShare share = new CloudFileShare(this.testShare.Uri, creds); + CloudFile testFile2 = share.GetRootDirectoryReference().GetFileReference("file2"); + + OperationContext context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await UploadTextAsync(testFile2, "file", Encoding.UTF8, operationContext:context), + context, + "Writing to a file while SAS does not allow for writing", + HttpStatusCode.Forbidden, + ""); + + CloudFile testFile3 = this.testShare.GetRootDirectoryReference().GetFileReference("file3"); + await testFile3.CreateAsync(0); + await TestAccess(sasToken2, SharedAccessFilePermissions.Read, null, this.testShare, testFile); + } + + [TestMethod] + [Description("Test all combinations of file permissions against a share")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileShareSASCombinationsAsync() + { + for (int i = 1; i < 0x20; i++) + { + SharedAccessFilePermissions permissions = (SharedAccessFilePermissions)i; + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = permissions, + }; + string sasToken = this.testShare.GetSharedAccessSignature(policy); + + CloudFile testFile = this.testShare.GetRootDirectoryReference().GetFileReference("file" + i); + await TestAccess(sasToken, permissions, null, this.testShare, testFile); + } + } + + [TestMethod] + [Description("Test all combinations of file permissions against a file")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileSASCombinationsAsync() + { + for (int i = 1; i < 0x20; i++) + { + CloudFile testFile = this.testShare.GetRootDirectoryReference().GetFileReference("file" + i); + SharedAccessFilePermissions permissions = (SharedAccessFilePermissions)i; + await TestFileSAS(testFile, permissions, null); + } + } + + [TestMethod] + [Description("Test all combinations of file permissions against a file")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileSASHeadersAsync() + { + for (int i = 1; i < 0x20; i++) + { + CloudFile testFile = this.testShare.GetRootDirectoryReference().GetFileReference("file" + i); + SharedAccessFilePermissions permissions = (SharedAccessFilePermissions)i; + SharedAccessFileHeaders headers = new SharedAccessFileHeaders() + { + CacheControl = "no-transform", + ContentDisposition = "attachment", + ContentEncoding = "gzip", + ContentLanguage = "tr,en", + ContentType = "text/html" + }; + + await TestFileSAS(testFile, permissions, headers); + } + } + + [TestMethod] + [Description("Test SAS against a file directory")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileDirectorySASAsync() + { + CloudFileDirectory dir = this.testShare.GetRootDirectoryReference().GetDirectoryReference("dirfile"); + CloudFile file = dir.GetFileReference("dirfile"); + + await dir.CreateAsync(); + await file.CreateAsync(512); + + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + Permissions = SharedAccessFilePermissions.Read | SharedAccessFilePermissions.List + }; + + string sasToken = file.GetSharedAccessSignature(policy); + CloudFileDirectory sasDir = new CloudFileDirectory(new Uri(dir.Uri.AbsoluteUri + sasToken)); + OperationContext context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await sasDir.FetchAttributesAsync(null /* accessCondition */, null /* options */, context), + context, + "Fetching attributes of a directory using a file SAS should fail", + HttpStatusCode.Forbidden, + ""); + + sasToken = this.testShare.GetSharedAccessSignature(policy); + sasDir = new CloudFileDirectory(new Uri(dir.Uri.AbsoluteUri + sasToken)); + await sasDir.FetchAttributesAsync(); + } + + [TestMethod] + [Description("Perform a SAS request specifying a shared protocol and ensure that everything works properly.")] + [TestCategory(ComponentCategory.File)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task CloudFileSASSharedProtocolsQueryParamAsync() + { + CloudFileShare share = GetRandomShareReference(); + try + { + await share.CreateAsync(); + CloudFile file; + SharedAccessFilePolicy policy = new SharedAccessFilePolicy() + { + Permissions = SharedAccessFilePermissions.Read, + SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5), + SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(30), + }; + + CloudFile fileWithKey = share.GetRootDirectoryReference().GetFileReference("filefile"); + byte[] data = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + byte[] target = new byte[4]; + await fileWithKey.UploadFromByteArrayAsync(data, 0, 4); + + foreach (SharedAccessProtocol? protocol in new SharedAccessProtocol?[] { null, SharedAccessProtocol.HttpsOrHttp, SharedAccessProtocol.HttpsOnly }) + { + string fileToken = fileWithKey.GetSharedAccessSignature(policy, null, null, protocol, null); + StorageCredentials fileSAS = new StorageCredentials(fileToken); + Uri fileSASUri = new Uri(fileWithKey.Uri + fileSAS.SASToken); + StorageUri fileSASStorageUri = new StorageUri(new Uri(fileWithKey.StorageUri.PrimaryUri + fileSAS.SASToken), new Uri(fileWithKey.StorageUri.SecondaryUri + fileSAS.SASToken)); + + int httpPort = fileSASUri.Port; + int securePort = 443; + + if (!string.IsNullOrEmpty(TestBase.TargetTenantConfig.FileSecurePortOverride)) + { + securePort = Int32.Parse(TestBase.TargetTenantConfig.FileSecurePortOverride); + } + + var schemesAndPorts = new[] { + new { scheme = "HTTP", port = httpPort}, + new { scheme = "HTTPS", port = securePort} + }; + + foreach (var item in schemesAndPorts) + { + fileSASUri = TransformSchemeAndPort(fileSASUri, item.scheme, item.port); + fileSASStorageUri = new StorageUri(TransformSchemeAndPort(fileSASStorageUri.PrimaryUri, item.scheme, item.port), TransformSchemeAndPort(fileSASStorageUri.SecondaryUri, item.scheme, item.port)); + + if (protocol.HasValue && protocol == SharedAccessProtocol.HttpsOnly && string.CompareOrdinal(item.scheme, "HTTP") == 0) + { + file = new CloudFile(fileSASUri); + OperationContext context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await file.FetchAttributesAsync(null /* accessCondition */, null /* options */, context), + context, + "Access a file using SAS with a shared protocols that does not match", + HttpStatusCode.Unused, + ""); + + file = new CloudFile(fileSASStorageUri, null); + context = new OperationContext(); + await TestHelper.ExpectedExceptionAsync( + async () => await file.FetchAttributesAsync(null /* accessCondition */, null /* options */, context), + context, + "Access a file using SAS with a shared protocols that does not match", + HttpStatusCode.Unused, + ""); + } + else + { + file = new CloudFile(fileSASUri); + await file.DownloadRangeToByteArrayAsync(target, 0, 0, 4, null, null, null); + for (int i = 0; i < 4; i++) + { + Assert.AreEqual(data[i], target[i]); + } + + file = new CloudFile(fileSASStorageUri, null); + await file.DownloadRangeToByteArrayAsync(target, 0, 0, 4, null, null, null); + for (int i = 0; i < 4; i++) + { + Assert.AreEqual(data[i], target[i]); + } + } + } + } + } + finally + { + share.DeleteIfExistsAsync().AsTask().Wait(); + } + } + + private static Uri TransformSchemeAndPort(Uri input, string scheme, int port) + { + UriBuilder builder = new UriBuilder(input); + builder.Scheme = scheme; + builder.Port = port; + return builder.Uri; + } + } +} diff --git a/Test/WindowsRuntime/File/FileTestBase.cs b/Test/WindowsRuntime/File/FileTestBase.cs index be7fd0302..c28346b3b 100644 --- a/Test/WindowsRuntime/File/FileTestBase.cs +++ b/Test/WindowsRuntime/File/FileTestBase.cs @@ -16,13 +16,27 @@ // ----------------------------------------------------------------------------------------- using System; +using System.IO; +using System.Text; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.WindowsAzure.Storage.Blob; namespace Microsoft.WindowsAzure.Storage.File { public partial class FileTestBase : TestBase { + public static async Task WaitForCopyAsync(CloudFile file) + { + bool copyInProgress = true; + while (copyInProgress) + { + await Task.Delay(1000); + await file.FetchAttributesAsync(); + copyInProgress = (file.CopyState.Status == CopyStatus.Pending); + } + } + public static async Task> CreateFilesAsync(CloudFileShare share, int count) { string name; @@ -50,5 +64,26 @@ public static async Task> ListFilesAndDirectoriesAsyn while (token != null); return results; } + + public static async Task UploadTextAsync(CloudFile file, string text, Encoding encoding, AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + { + byte[] textAsBytes = encoding.GetBytes(text); + using (MemoryStream stream = new MemoryStream()) + { + stream.Write(textAsBytes, 0, textAsBytes.Length); + stream.Seek(0, SeekOrigin.Begin); + file.ServiceClient.DefaultRequestOptions.ParallelOperationThreadCount = 2; + await file.UploadFromStreamAsync(stream.AsInputStream(), accessCondition, options, operationContext); + } + } + + public static async Task DownloadTextAsync(CloudFile file, Encoding encoding, AccessCondition accessCondition = null, FileRequestOptions options = null, OperationContext operationContext = null) + { + using (MemoryStream stream = new MemoryStream()) + { + await file.DownloadToStreamAsync(stream.AsOutputStream(), accessCondition, options, operationContext); + return encoding.GetString(stream.ToArray(), 0, (int)stream.Length); + } + } } } diff --git a/Test/WindowsRuntime/Microsoft.WindowsAzure.StorageRT.Test.csproj b/Test/WindowsRuntime/Microsoft.WindowsAzure.StorageRT.Test.csproj index c5ce4a745..2cee33df6 100644 --- a/Test/WindowsRuntime/Microsoft.WindowsAzure.StorageRT.Test.csproj +++ b/Test/WindowsRuntime/Microsoft.WindowsAzure.StorageRT.Test.csproj @@ -89,6 +89,9 @@ + + + diff --git a/Test/WindowsRuntime/Properties/AssemblyInfo.cs b/Test/WindowsRuntime/Properties/AssemblyInfo.cs index 1e40e653d..584f3e359 100644 --- a/Test/WindowsRuntime/Properties/AssemblyInfo.cs +++ b/Test/WindowsRuntime/Properties/AssemblyInfo.cs @@ -24,6 +24,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("6.0.1.0")] -[assembly: AssemblyFileVersion("6.0.1.0")] +[assembly: AssemblyVersion("6.1.0.0")] +[assembly: AssemblyFileVersion("6.1.0.0")] [assembly: ComVisible(false)] diff --git a/Test/WindowsRuntime/Table/TableOperationUnitTests.cs b/Test/WindowsRuntime/Table/TableOperationUnitTests.cs index b8c916be0..b60a7294b 100644 --- a/Test/WindowsRuntime/Table/TableOperationUnitTests.cs +++ b/Test/WindowsRuntime/Table/TableOperationUnitTests.cs @@ -821,6 +821,298 @@ private async Task DoTableBatchRetrieveWithResolverAsync(TablePayloadFormat payl Assert.AreEqual((string)result.Result, sendEnt.PartitionKey + sendEnt.RowKey + sendEnt.Properties["foo"].StringValue + ComplexEntity.NumberOfNonNullProperties); } + [TestMethod] + [Description("A test to check retrieve projection functionality selecting all valid columns.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task TableRetrieveWithFullProjectionAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableRetrieveWithFullProjectionAsync(payloadFormat); + } + } + + private async Task DoTableRetrieveWithFullProjectionAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert entity + DynamicTableEntity sendEnt = new DynamicTableEntity(); + sendEnt.PartitionKey = Guid.NewGuid().ToString(); + sendEnt.RowKey = Guid.NewGuid().ToString(); + sendEnt.Properties = new ComplexEntity().WriteEntity(null); + await currentTable.ExecuteAsync(TableOperation.Insert(sendEnt)); + + TableRequestOptions options = new TableRequestOptions() + { + PropertyResolver = (partitionKey, rowKey, propName, propValue) => ComplexEntity.ComplexEntityPropertyResolver(partitionKey, rowKey, propName, propValue) + }; + + List selectedColumns = new List(); + foreach (string key in sendEnt.Properties.Keys) + { + // Exclude null params since the service will ignore these on entity creation. + if (!key.Contains("Null")) + { + selectedColumns.Add(key); + } + } + + // Retrieve entity + TableResult result = await currentTable.ExecuteAsync(TableOperation.Retrieve(sendEnt.PartitionKey, sendEnt.RowKey, selectedColumns), options, null); + Assert.AreEqual(result.HttpStatusCode, (int)HttpStatusCode.OK); + DynamicTableEntity retrievedEntity = result.Result as DynamicTableEntity; + + // Validate entity + foreach (string key in selectedColumns) + { + Assert.AreEqual(sendEnt.Properties[key], retrievedEntity.Properties[key]); + } + } + + [TestMethod] + [Description("A test to check retrieve projection functionality selecting all valid columns.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task TableRetrieveWithFullProjectionWithEmptySelectAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableRetrieveWithFullProjectionWithEmptySelectAsync(payloadFormat); + } + } + + private async Task DoTableRetrieveWithFullProjectionWithEmptySelectAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert entity + DynamicTableEntity sendEnt = new DynamicTableEntity(); + sendEnt.PartitionKey = Guid.NewGuid().ToString(); + sendEnt.RowKey = Guid.NewGuid().ToString(); + sendEnt.Properties = new ComplexEntity().WriteEntity(null); + await currentTable.ExecuteAsync(TableOperation.Insert(sendEnt)); + + TableRequestOptions options = new TableRequestOptions() + { + PropertyResolver = (partitionKey, rowKey, propName, propValue) => ComplexEntity.ComplexEntityPropertyResolver(partitionKey, rowKey, propName, propValue) + }; + + List selectedColumns = new List(); + + // Retrieve entity + TableResult result = await currentTable.ExecuteAsync(TableOperation.Retrieve(sendEnt.PartitionKey, sendEnt.RowKey, selectedColumns), options, null); + Assert.AreEqual(result.HttpStatusCode, (int)HttpStatusCode.OK); + DynamicTableEntity retrievedEntity = result.Result as DynamicTableEntity; + + // Validate entity + foreach (string key in retrievedEntity.Properties.Keys) + { + Assert.AreEqual(sendEnt.Properties[key], retrievedEntity.Properties[key]); + } + } + + [TestMethod] + [Description("A test to check retrieve projection functionality selecting only a subset of the columns.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task TableRetrieveWithPartialProjectionAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableRetrieveWithPartialProjectionAsync(payloadFormat); + } + } + + private async Task DoTableRetrieveWithPartialProjectionAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert entity + DynamicTableEntity sendEnt = new DynamicTableEntity(); + sendEnt.PartitionKey = Guid.NewGuid().ToString(); + sendEnt.RowKey = Guid.NewGuid().ToString(); + sendEnt.Properties = new ComplexEntity().WriteEntity(null); + await currentTable.ExecuteAsync(TableOperation.Insert(sendEnt)); + + TableRequestOptions options = new TableRequestOptions() + { + PropertyResolver = (partitionKey, rowKey, propName, propValue) => ComplexEntity.ComplexEntityPropertyResolver(partitionKey, rowKey, propName, propValue) + }; + + List selectedColumns = new List + { + "Double", + "IntegerPrimitive" + }; + + // Retrieve entity + TableResult result = await currentTable.ExecuteAsync(TableOperation.Retrieve(sendEnt.PartitionKey, sendEnt.RowKey, selectedColumns), options, null); + Assert.AreEqual(result.HttpStatusCode, (int)HttpStatusCode.OK); + DynamicTableEntity retrievedEntity = result.Result as DynamicTableEntity; + + // Validate entity + foreach (string key in sendEnt.Properties.Keys) + { + if (!selectedColumns.Contains(key)) + { + Assert.IsFalse(retrievedEntity.Properties.ContainsKey(key)); + } + else + { + Assert.AreEqual(sendEnt.Properties[key], retrievedEntity.Properties[key]); + } + } + } + + [TestMethod] + [Description("A test to check retrieve projection functionality edge cases by comparing query results with retrieve results.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task TableRetrieveWithProjectionEdgeCasesAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableRetrieveWithProjectionEdgeCasesAsync(payloadFormat); + } + } + + private async Task DoTableRetrieveWithProjectionEdgeCasesAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert entity + DynamicTableEntity sendEnt = new DynamicTableEntity(); + sendEnt.PartitionKey = Guid.NewGuid().ToString(); + sendEnt.RowKey = Guid.NewGuid().ToString(); + sendEnt.Properties = new ComplexEntity().WriteEntity(null); + await currentTable.ExecuteAsync(TableOperation.Insert(sendEnt)); + + TableRequestOptions options = new TableRequestOptions() + { + PropertyResolver = (partitionKey, rowKey, propName, propValue) => ComplexEntity.ComplexEntityPropertyResolver(partitionKey, rowKey, propName, propValue) + }; + + List selectedColumns = new List + { + "BoolPrimitive", + "InvalidProperty" + }; + + // Query entity + TableQuery query = new TableQuery().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sendEnt.PartitionKey)).Select(selectedColumns); + TableQuerySegment retrievedEntityByQuery = await currentTable.ExecuteQuerySegmentedAsync(query, null, options, null); + + // Retrieve entity + TableResult result = await currentTable.ExecuteAsync(TableOperation.Retrieve(sendEnt.PartitionKey, sendEnt.RowKey, selectedColumns), options, null); + DynamicTableEntity retrievedEntityByRetrieve = result.Result as DynamicTableEntity; + + // Validate entity + Assert.AreEqual(retrievedEntityByQuery.Results[0].Properties["BoolPrimitive"], retrievedEntityByRetrieve.Properties["BoolPrimitive"]); + Assert.AreEqual(retrievedEntityByQuery.Results[0].Properties["InvalidProperty"], retrievedEntityByRetrieve.Properties["InvalidProperty"]); + Assert.AreEqual(retrievedEntityByRetrieve.Properties["BoolPrimitive"].PropertyType, EdmType.Boolean); + Assert.AreEqual(retrievedEntityByRetrieve.Properties["InvalidProperty"].PropertyType, EdmType.String); + Assert.AreEqual(retrievedEntityByRetrieve.Properties["InvalidProperty"].StringValue, null); + } + + [TestMethod] + [Description("A test to check retrieve projection functionality with the overload of type TableResult.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task TableRetrieveWithTableResultOverloadWithProjectionAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableRetrieveWithTableResultOverloadWithProjectionAsync(payloadFormat); + } + } + + private async Task DoTableRetrieveWithTableResultOverloadWithProjectionAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert entity + DynamicTableEntity sendEnt = new DynamicTableEntity(); + sendEnt.PartitionKey = Guid.NewGuid().ToString(); + sendEnt.RowKey = Guid.NewGuid().ToString(); + sendEnt.Properties = new ComplexEntity().WriteEntity(null); + sendEnt.Properties.Add("foo", new EntityProperty("bar")); + await currentTable.ExecuteAsync(TableOperation.Insert(sendEnt)); + + EntityResolver resolver = (pk, rk, ts, props, etag) => pk + rk + props["foo"].StringValue + props.Count; + + List selectedColumns = new List(); + foreach (string key in sendEnt.Properties.Keys) + { + // Exclude null params since the service will ignore these on entity creation. + if (!key.Contains("Null")) + { + selectedColumns.Add(key); + } + } + + // Retrieve entity + TableResult result = await currentTable.ExecuteAsync(TableOperation.Retrieve(sendEnt.PartitionKey, sendEnt.RowKey, resolver, selectedColumns)); + Assert.AreEqual(result.HttpStatusCode, (int)HttpStatusCode.OK); + + // Since there are properties in ComplexEntity set to null, we do not receive those from the server. Hence we need to check for non null values. + Assert.AreEqual((string)result.Result, sendEnt.PartitionKey + sendEnt.RowKey + sendEnt.Properties["foo"].StringValue + ComplexEntity.NumberOfNonNullProperties); + } + + [TestMethod] + [Description("A test to check retrieve projection functionality with the generic overload.")] + [TestCategory(ComponentCategory.Table)] + [TestCategory(TestTypeCategory.UnitTest)] + [TestCategory(SmokeTestCategory.NonSmoke)] + [TestCategory(TenantTypeCategory.DevStore), TestCategory(TenantTypeCategory.DevFabric), TestCategory(TenantTypeCategory.Cloud)] + public async Task TableRetrieveWithGenericOverloadWithProjectionAsync() + { + foreach (TablePayloadFormat payloadFormat in Enum.GetValues(typeof(TablePayloadFormat))) + { + await DoTableRetrieveWithGenericOverloadWithProjectionAsync(payloadFormat); + } + } + + private async Task DoTableRetrieveWithGenericOverloadWithProjectionAsync(TablePayloadFormat format) + { + tableClient.DefaultRequestOptions.PayloadFormat = format; + + // Insert entity + string pk = Guid.NewGuid().ToString(); + string rk = Guid.NewGuid().ToString(); + ComplexEntity sendEnt = new ComplexEntity(pk, rk); + sendEnt.String = "ResetTestTotested"; + sendEnt.Double = (Double)5678.5678; + sendEnt.IntegerPrimitive = 5678; + await currentTable.ExecuteAsync(TableOperation.Insert(sendEnt)); + + List selectedColumns = new List + { + "Double", + "IntegerPrimitive" + }; + + // Retrieve entity + TableResult result = await currentTable.ExecuteAsync(TableOperation.Retrieve(sendEnt.PartitionKey, sendEnt.RowKey, selectedColumns)); + ComplexEntity retEntity = result.Result as ComplexEntity; + + // Validate entity + Assert.AreEqual(retEntity.Double, sendEnt.Double); + Assert.AreEqual(retEntity.IntegerPrimitive, sendEnt.IntegerPrimitive); + Assert.AreNotEqual(retEntity.String, sendEnt.String); + } + [TestMethod] [Description("A test to check ignore property attribute while serializing an entity")] [TestCategory(ComponentCategory.Table)] diff --git a/Tools/nuget.exe b/Tools/nuget.exe index 8dd7e45ae..324daa842 100644 Binary files a/Tools/nuget.exe and b/Tools/nuget.exe differ diff --git a/changelog.txt b/changelog.txt index a0155fd46..e71fef6b3 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,13 @@ +Changes in 6.1.0 : + + - All: Refreshed ASP.NET 5 targets to work with latest RC bits (DNX 4.5.1 and DNX Core 5.0, beta8). + - All: Added support for Account Shared Access Signatures to the PCL, enabling scenarios such as ListContainers, CreateTable, GetQueueServiceProperties, etc. + - Blobs: Added a performance enhancement - uploading data using client-side encryption used to always call PutBlock + PutblockList, even if the library should be calling PutBlob. + - Blobs: Fixed a bug where uploading data using client-side encryption, async or APM, and with an access condition, will in some cases upload the data unencrypted. + - Tables: Added support for projecting columns with the Retrieve operation. + - Tables: Fixed a bug where some table queries with client-side decryption were not formatted correctly, resulting in empty entity results. + - Files: Fixed bug in Windows Runtime where FetchAttributes API did not update copyState. + Changes in 6.0.0 : - All: Support for 2015-04-05 REST version. Please see our REST API documentation and blogs for information about the related added features. diff --git a/global.json b/global.json index 2ce19e975..f057def1f 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "projects": [ "lib", "Lib/AspNet" ], "sdk": { - "version": "1.0.0-beta7" + "version": "1.0.0-beta8" } }