3
votes

I know this a question that have been asked many times. But I simply cannot get it to work reliably.

I get this exception:

Microsoft.WindowsAzure.Storage.StorageException was unhandled by user code
  HResult=-2146233088 Message=The remote server returned an error: NotFound.
  Source=Microsoft.WindowsAzure.Storage StackTrace: at Microsoft.WindowsAzure.Storage.Core.Util.StorageAsyncResult\`1.End()
       at Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.EndUploadFromStream(IAsyncResult asyncResult) at Microsoft.WindowsAzure.Storage.Core.Util.AsyncExtensions.<>c__DisplayClass4.<CreateCallbackVoid>b__3(IAsyncResult ar)

--- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
       at MVVMTestApp.View.ShieldCreator.<OnNavigatedFrom>d__7.MoveNext()
  InnerException: System.Net.WebException
       HResult=-2146233079
       Message=The remote server returned an error: NotFound.
       Source=Microsoft.WindowsAzure.Storage
       StackTrace:
            at Microsoft.WindowsAzure.Storage.Shared.Protocol.HttpResponseParsers.ProcessExpectedStatusCodeNoException[T](HttpStatusCode expectedStatusCode, HttpStatusCode actualStatusCode, T retVal, StorageCommandBase\`1 cmd, Exception ex)
            at Microsoft.WindowsAzure.Storage.Shared.Protocol.HttpResponseParsers.ProcessExpectedStatusCodeNoException[T](HttpStatusCode expectedStatusCode, HttpWebResponse resp, T retVal, StorageCommandBase\`1 cmd, Exception ex)
            at Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob.<>c__DisplayClass39.<PutBlobImpl>b__38(RESTCommand`1 cmd, HttpWebResponse resp, Exception ex, OperationContext ctx)
            at Microsoft.WindowsAzure.Storage.Core.Executor.Executor.EndGetResponse[T](IAsyncResult getResponseResult)
       InnerException: System.Net.WebException
            HResult=-2146233079
            Message=The remote server returned an error: NotFound.
            Source=System.Windows
            StackTrace:
                 at System.Net.Browser.ClientHttpWebRequest.InternalEndGetResponse(IAsyncResult asyncResult)
                 at System.Net.Browser.ClientHttpWebRequest.<>c__DisplayClasse.<EndGetResponse>b__d(Object sendState)
                 at System.Net.Browser.AsyncHelper.<>c__DisplayClass1.<BeginOnUI>b__0(Object sendState)
            InnerException: 

The code I am using to upload to the server is:

 MemoryStream stream = new MemoryStream();
        if (bi != null)
        {
            WriteableBitmap bmp = new WriteableBitmap((BitmapSource)bi);
            bmp.SaveJpeg(stream, bmp.PixelWidth, bmp.PixelHeight, 0, 100);
        }
        stream.Seek(0, SeekOrigin.Begin);
        DTO.PictureStorage uploadImage = new DTO.PictureStorage();
        uploadImage.UserId = App.UserInformationID;
        uploadImage.ContainerName = ("crestimage" + App.UserInformationID.Replace(":", "")).ToLower();
        uploadImage.ResourceName = Guid.NewGuid().ToString().ToLower() + ".jpg";

        var resultPicture = await App.MobileService.InvokeApiAsync<DTO.PictureStorage, DTO.PictureStorage>("user/blobStorage", uploadImage);
        uploadImage = resultPicture;
        // If we have a returned SAS, then upload the blob.
        if (!string.IsNullOrEmpty(uploadImage.SasQueryString))
        {
            // Get the URI generated that contains the SAS 
            // and extract the storage credentials.
            StorageCredentials cred = new StorageCredentials(uploadImage.SasQueryString);
            var imageUri = new Uri(uploadImage.ImageUri);

            // Instantiate a Blob store container based on the info in the returned item.
            CloudBlobContainer container = new CloudBlobContainer(
                new Uri(string.Format("http://{0}/{1}",
                    imageUri.Host.ToLower(), uploadImage.ContainerName.ToLower())), cred);

            // Upload the new image as a BLOB from the stream.
            CloudBlockBlob blobFromSASCredential =
                container.GetBlockBlobReference(uploadImage.ResourceName);
            await blobFromSASCredential.UploadFromStreamAsync(stream);  //The exception is thrown here

            // When you request an SAS at the container-level instead of the blob-level,
            // you are able to upload multiple streams using the same container credentials.

            stream = null;
        }

On the server I have this code:

 string storageAccountName;
                string storageAccountKey;

                // Try to get the Azure storage account token from app settings.  
                if (!(Services.Settings.TryGetValue("STORAGE_ACCOUNT_NAME", out storageAccountName) |
                Services.Settings.TryGetValue("STORAGE_ACCOUNT_ACCESS_KEY", out storageAccountKey)))
                {
                    Services.Log.Error("Could not retrieve storage account settings.");
                }

                // Set the URI for the Blob Storage service.
                Uri blobEndpoint = new Uri(string.Format("https://{0}.blob.core.windows.net", storageAccountName));

                // Create the BLOB service client.
                CloudBlobClient blobClient = new CloudBlobClient(blobEndpoint,
                    new StorageCredentials(storageAccountName, storageAccountKey));

                if (item.ContainerName != null)
                {
                    // Set the BLOB store container name on the item, which must be lowercase.
                    item.ContainerName = item.ContainerName.ToLower();

                    // Create a container, if it doesn't already exist.
                    CloudBlobContainer container = blobClient.GetContainerReference(item.ContainerName);

                    try
                    {
                        await container.DeleteIfExistsAsync();
                        Services.Log.Info("Deleted.");
                    }
                    catch 
                    {
                        Services.Log.Info("Could not DeleteIfExist.");
                    }
                    await container.CreateIfNotExistsAsync();

                    // Create a shared access permission policy. 
                    BlobContainerPermissions containerPermissions = new BlobContainerPermissions();

                    // Enable anonymous read access to BLOBs.
                    containerPermissions.PublicAccess = BlobContainerPublicAccessType.Blob;
                    container.SetPermissions(containerPermissions);

                    // Define a policy that gives write access to the container for 5 minutes.                                   
                    SharedAccessBlobPolicy sasPolicy = new SharedAccessBlobPolicy()
                    {
                        SharedAccessStartTime = DateTime.UtcNow,
                        SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(5),
                        Permissions = SharedAccessBlobPermissions.Write
                    };

                    // Get the SAS as a string.
                    item.SasQueryString = container.GetSharedAccessSignature(sasPolicy);

                    // Set the URL used to store the image.
                    item.ImageUri = string.Format("{0}{1}/{2}", blobEndpoint.ToString(),
                        item.ContainerName, item.ResourceName);
                }

                // Complete the insert operation.
                user.ContainerName = item.ContainerName;
                user.ResourceName = item.ResourceName;
                user.SasQueryString = item.SasQueryString;
                user.ImageUri = item.ImageUri;
                user.Update = DateTime.UtcNow.AddMinutes(5);

                db.SaveChanges();

I introduced the delete await based on this answer: https://stackoverflow.com/a/3221638/2076775

Hope somebody can tell me what stupid mistake I am making because I cannot find it. And unfortunately I cannot get fiddler to work (company policy and proxy settings :S).

The aim of the code is to upload an image, and it should be possible to do it again and again to the same address if the user changes the image.

1
What operation is returning a 404?Greg D
Sorry should have been specified: CloudBlockBlob blobFromSASCredential = container.GetBlockBlobReference(uploadImage.ResourceName); await blobFromSASCredential.UploadFromStreamAsync(stream); I have marked it in the code nowJTIM

1 Answers

2
votes

Your server code is using current time as SAS start time, which can cause authentication issues because of clock skew. In other words, if the Blob Service's current time is behind your server's current time, the SAS token will not be valid at that point. If you do not have a hard limit on start time, it is recommended that you omit it completely.