Windows 8 : Azure Storage Explorer

Alors que nous venons de passer (pour les abonnés MSDN!) en version RTM de Windows 8 et VS2012, nous n’avons pas encore de SDK Azure pour le Windows Runtime, en particulier pour l’accès au Blob storage. Actuellement pour y accéder et gérer le contenu nous pouvons utiliser les services REST de l’Azure Storage.

Dans cet article nous aborderons les sujets suivants :

  • Accès aux services REST de l’Azure Storage via la classe HttpClient et authentification des requêtes via les API Windows.Security.Cryptography
  • Utilisation des contrôles FileOpenPicker et FileSavePicker
  • Mise en place des contrats File Open Picker, File Save Picker et Cached File Updater

Vous pouvez télécharger le code source au format VS2012 RTM ici : AzureStorageExplorerApp.zip

Pour le repasser en VS2012 RC, je pense qu’il vous suffit d’éditer en XML le manifeste et de modifier les attributs OSMinVersion et OSMaxVersion à 6.2.0.

Pour l’exécuter il vous faudra éditer les constantes AccountStorageName et AccountKey de la classe App avec un compte de stockage Azure valide. Ci-dessous un visuel de l’application :

image

 

Accès aux services REST Azure

Pour accéder à un compte Windows Azure via les services REST, nous avons besoin d’une connexion internet bien sûr, d’un compte azure et de sa clé.

En terme d’API, nous utilisons la classe HttpClient pour envoyer nos requêtes. Et en fonction de l’opération, nous utilisons les méthodes suivantes :

  • Lister les éléments : GetStringAsync(Uri),
  • Télécharger un blob : GetStreamAsync(Uri)
  • Envoyer un blob : PutAsync(Uri), DeleteAsync(Uri)

Les entêtes Http

Toutes ces opérations nécessitent les entêtes Http suivantes :

  • x-ms-date : Date au format UTC
  • x-ms-version : numéro de version des services REST Azure
  • Authorization : clé signé d’authentification

Dans le cadre d’un envoi de blob il en faudra 3 supplémentaires :

  • x-ms-blob-type : type de blob à télécharger, “BlockBlob” ou “PageBlob”
  • Accept-Charset : identifie l’encodage
  • Content-Lenght : précise la taille du blob à envoyer

Et si nous envoyons un Page Blob alors il faut encore des informations supplémentaires sur sa taille et l’identification du numéro de page que l’on envoie :

  • x-ms-blob-content-lenght : représente la taille maximale d’une page de blob
  • x-ms-blob-sequence-number : numéro de séquence de la page utilisé par le développeur pour envoyer ses pages au service

D’autres entêtes peuvent être ajoutées mais sont optionnelles. Vous retrouverez tous les détails dans la documentation officielle des services REST Azure .

Le code ci-dessous illustre la construction d’une requête Http permettant de lister les containeurs d’un compte de stockage Azure :

  1. // Compte de stockage Azure
  2. var accountStorageName = « win8sample » ;
  3. var accountKey = « XXXXXXXXXXXXXXXXXXXXXXX » ;
  4. var now = DateTime .UtcNow;
  5. // Uri pour lister les containeurs
  6. Uri uri = new Uri ( « http:// » + storageAccountName + « .blob.core.windows.net/?comp=list » );
  7. var httpClient = new HttpClient ();
  8. // Ajout des Entetes Http
  9. httpClient.DefaultRequestHeaders.Add( « x-ms-date » , now.ToString( « R » , System.Globalization. CultureInfo .InvariantCulture));
  10. httpClient.DefaultRequestHeaders.Add( « x-ms-version » , « 2009-09-19 » );
  11. // Cration de la signature et ajout de l’entete d’authentification
  12. var signature = RequestHeadersHelper .GetSignature( « GET » , now, httpClient, uri, storageAccountName, storageKey);
  13. httpClient.DefaultRequestHeaders.Add( « Authorization » , « SharedKey  » + storageAccountName + « : » + signature);
  14. // Appel du service REST et recuperation du resultat sous forme de string
  15. var response = await httpClient.GetStringAsync(uri);

Authentification de la requête

L’entête Authorization permet de signer la requête. Dans notre cas elle est construite de la manière suivante : [Type de clé] [Compte de stockage]:[Signature]

Cette entête peut se construire différemment selon la version des services REST que nous utilisons (x-ms-version). Dans notre cas nous spécifions que nous utilisons la méthode SharedKey pour la signature.

Cette méthode consiste à créer une chaine de caractères représentant la signature de la requête. Une fois cette signature obtenue, il suffit de l’encoder avec l’algorithme HMAC-SHA256. La valeur de hachage ainsi obtenu représente la signature à ajouter dans l’entête d’authentification.

La signature se compose ainsi :

  1. string stringToSign = VERB + « \n » +
  2. Content- Encoding + « \n »
  3. Content-Language + « \n »
  4. Content-Length + « \n »
  5. Content-MD5 + « \n » +
  6. Content- Type + « \n » +
  7. Date + « \n » +
  8. If-Modified-Since + « \n »
  9. If-Match + « \n »
  10. If-None-Match + « \n »
  11. If-Unmodified-Since + « \n »
  12. Range + « \n »
  13. CanonicalizedHeaders +
  14. CanonicalizedResource;

 

Dans notre exemple, la signature de la requête permettant de lister les containeurs du compte Azure blaut est la suivante :

  1. string signature = « GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:Sun, 19 Aug 2012 16:43:57 GMT\nx-ms-version:2009-09-19\n/blaut/\ncomp:list » ;

 

Une fois que nous avons cette signature il nous faut l’encoder via l’algorithme HMAC-SHA256, que nous obtenons via la classe MacAlgorithmProvider dans l’espace de nom Windows.Security.Cryptography.Core :

  1. MacAlgorithmProvider hmacSha256 = MacAlgorithmProvider .OpenAlgorithm( MacAlgorithmNames .HmacSha256);

 

Nous avons besoin d’une clé de chiffrement de type CryptographicKey pour chiffrer la signature. Pour obtenir cette clé, il faut récupérer sous forme binaire la clé du compte de stockage Azure puis appeler la méthode CreateKey de l’algorithme de hachage :

  1. // Dcodage de la cl de stockage Azure
  2. IBuffer storageKeyBuffer = CryptographicBuffer .DecodeFromBase64String(storageKey);
  3. // Cration d’une cl de chiffrement partir de la cl de stockage Azure
  4. CryptographicKey cryptoKey = hmacSha256.CreateKey(storageKeyBuffer);

 

Il ne reste plus qu’à convertir la signature en binaire pour pouvoir la signer avec la clé. Puis convertir le binaire résultant en chaine de caractères Base64 :

  1. // Conversion de la signature en binaire
  2. IBuffer signatureBuffer = CryptographicBuffer .ConvertStringToBinary(signature, BinaryStringEncoding .Utf8);
  3. // Chiffrement de la signature
  4. IBuffer hashResult = CryptographicEngine .Sign(cryptoKey, signatureBuffer);
  5. // Recuperation de la valeur de hachage sous forme d’une string en Base64
  6. return CryptographicBuffer .EncodeToBase64String(hashResult);

AzureBlobStorageRestServices

Ci-dessous le code complet de la classe permettant d’accéder à son compte de stockage Azure : lister les containeurs, lister les blobs, ajouter, télécharger ou supprimer un blob :

  1. public static class AzureBlobStorageRestServices
  2. {
  3. public async static Task < string > ListContainersAsync( string storageAccountName, string storageKey)
  4. {
  5. var now = DateTime .UtcNow;
  6. // Uri pour lister les containeurs
  7. Uri uri = new Uri ( « http:// &raquo; + storageAccountName + « .blob.core.windows.net/?comp=list » );
  8. var httpClient = new HttpClient ();
  9. // Ajout des Entetes Http
  10. httpClient.DefaultRequestHeaders.Add( « x-ms-date » , now.ToString( « R » , System.Globalization. CultureInfo .InvariantCulture));
  11. httpClient.DefaultRequestHeaders.Add( « x-ms-version » , « 2009-09-19 » );
  12. // Cration de la signature et ajout de l’entete d’authentification
  13. var signature = AzureBlobStorageRestServices .GetSignature( « GET » , now, httpClient, uri, storageAccountName, storageKey);
  14. httpClient.DefaultRequestHeaders.Add( « Authorization » , « SharedKey  » + storageAccountName + « : » + signature);
  15. // Appel du service REST et recuperation du resultat sous forme de string
  16. var response = await httpClient.GetStringAsync(uri);
  17. return response;
  18. }
  19. public async static Task < string > ListBlobsAsync( string containerName, string storageAccountName, string storageKey)
  20. {
  21. var now = DateTime .UtcNow;
  22. // Uri pour lister les blobs d’un containeur
  23. Uri uri = new Uri ( « http:// &raquo; + storageAccountName + « .blob.core.windows.net/ » + containerName + « ?restype=container&comp=list » );
  24. var httpClient = new HttpClient ();
  25. httpClient.DefaultRequestHeaders.Add( « x-ms-date » , now.ToString( « R » , System.Globalization. CultureInfo .InvariantCulture));
  26. httpClient.DefaultRequestHeaders.Add( « x-ms-version » , « 2009-09-19 » );
  27. var signature = AzureBlobStorageRestServices .GetSignature( « GET » , now, httpClient, uri, storageAccountName, storageKey);
  28. httpClient.DefaultRequestHeaders.Add( « Authorization » , « SharedKey  » + storageAccountName + « : » + signature);
  29. var message = await httpClient.GetStringAsync(uri);
  30. return message;
  31. }
  32. public async static Task < Stream > GetBlobAsync( string blobUri, string storageAccountName, string storageKey)
  33. {
  34. Uri uri = new Uri (blobUri);
  35. var now = DateTime .UtcNow;
  36. var httpClient = new HttpClient ();
  37. httpClient.DefaultRequestHeaders.Add( « x-ms-date » , now.ToString( « R » , System.Globalization. CultureInfo .InvariantCulture));
  38. httpClient.DefaultRequestHeaders.Add( « x-ms-version » , « 2009-09-19 » );
  39. var signature = AzureBlobStorageRestServices .GetSignature( « GET » , now, httpClient, uri, storageAccountName, storageKey);
  40. httpClient.DefaultRequestHeaders.Add( « Authorization » , « SharedKey  » + storageAccountName + « : » + signature);
  41. var stream = await httpClient.GetStreamAsync(blobUri);
  42. return stream;
  43. }
  44. public async static Task PutBlobAsync( Stream stream, string blobUri, string storageAccountName, string storageKey)
  45. {
  46. Uri uri = new Uri (blobUri);
  47. var now = DateTime .UtcNow;
  48. HttpClient httpClient = new HttpClient ();
  49. httpClient.DefaultRequestHeaders.Add( « x-ms-date » , now.ToString( « R » , System.Globalization. CultureInfo .InvariantCulture));
  50. httpClient.DefaultRequestHeaders.Add( « x-ms-version » , « 2009-09-19 » );
  51. httpClient.DefaultRequestHeaders.Add( « x-ms-blob-type » , « BlockBlob » );
  52. httpClient.DefaultRequestHeaders.Add( « Accept-Charset » , « UTF-8 » );
  53. httpClient.DefaultRequestHeaders.TryAddWithoutValidation( « Content-Length » , stream.Length.ToString());
  54. var signature = GetSignature( « PUT » , now, httpClient, uri, storageAccountName, storageKey, stream.Length);
  55. httpClient.DefaultRequestHeaders.Add( « Authorization » , « SharedKey  » + storageAccountName + « : » + signature);
  56. System.Net.Http. StreamContent content = new StreamContent (stream);
  57. var response = await httpClient.PutAsync(blobUri.ToString(), content);
  58. if (response.StatusCode != System.Net. HttpStatusCode .Created)
  59. throw new Exception ( « Unable to upload blob » );
  60. }
  61. public async static Task DeleteBlobAsync( string blobUri, string storageAccountName, string storageKey)
  62. {
  63. var now = DateTime .UtcNow;
  64. var uri = new Uri (blobUri);
  65. HttpClient httpClient = new HttpClient ();
  66. httpClient.DefaultRequestHeaders.Add( « x-ms-date » , now.ToString( « R » , System.Globalization. CultureInfo .InvariantCulture));
  67. httpClient.DefaultRequestHeaders.Add( « x-ms-version » , « 2009-09-19 » );
  68. var signature = AzureBlobStorageRestServices .GetSignature( « DELETE » , now, httpClient, uri, storageAccountName, storageKey, 0);
  69. httpClient.DefaultRequestHeaders.Add( « Authorization » , « SharedKey  » + storageAccountName + « : » + signature);
  70. var response = await httpClient.DeleteAsync(blobUri);
  71. if (response.StatusCode != System.Net. HttpStatusCode .Accepted)
  72. throw new Exception ( « Uanble to delete blob » );
  73. }
  74. private static string GetSignature( string method, DateTime now, HttpClient httpClient, Uri uri, string storageAccountName, string storageKey, long contentLenght = 0)
  75. {
  76. string messageSignature = String .Format( « {0}\n\n\n{1}\n\n\n\n\n\n\n\n\n{2}{3} » ,
  77. method,
  78. (method == « GET » || method == « HEAD » ) ? String .Empty : contentLenght.ToString(),
  79. GetCanonicalizedHeaders(httpClient),
  80. GetCanonicalizedResource(uri, storageAccountName));
  81. // Rcupration de l’algorithme HMAC-SHA256
  82. MacAlgorithmProvider hmacSha256 = MacAlgorithmProvider .OpenAlgorithm( MacAlgorithmNames .HmacSha256);
  83. // Dcodage de la cl de stockage Azure
  84. IBuffer storageKeyBuffer = CryptographicBuffer .DecodeFromBase64String(storageKey);
  85. // Cration d’une cl de chiffrement partir de la cl de stockage Azure
  86. CryptographicKey cryptoKey = hmacSha256.CreateKey(storageKeyBuffer);
  87. // Conversion de la signature en binaire
  88. IBuffer signatureBuffer = CryptographicBuffer .ConvertStringToBinary(messageSignature, BinaryStringEncoding .Utf8);
  89. // Chiffrement de la signature
  90. IBuffer hashResult = CryptographicEngine .Sign(cryptoKey, signatureBuffer);
  91. // Recuperation de la valeur de hachage sous forme d’une string en Base64
  92. return CryptographicBuffer .EncodeToBase64String(hashResult);
  93. }
  94. private static string GetCanonicalizedHeaders( HttpClient httpClient)
  95. {
  96. List < string > headerNameList = new List < string >();
  97. StringBuilder sb = new StringBuilder ();
  98. foreach ( var heeader in httpClient.DefaultRequestHeaders)
  99. {
  100. if (heeader.Key.ToLowerInvariant().StartsWith( « x-ms-«  , StringComparison .Ordinal))
  101. {
  102. headerNameList.Add(heeader.Key.ToLowerInvariant());
  103. }
  104. }
  105. headerNameList.Sort();
  106. foreach ( string headerName in headerNameList)
  107. {
  108. StringBuilder builder = new StringBuilder (headerName);
  109. string separator = « : » ;
  110. foreach ( string headerValue in GetHeaderValues(httpClient.DefaultRequestHeaders, headerName))
  111. {
  112. string trimmedValue = headerValue.Replace( « \r\n » , String .Empty);
  113. builder.Append(separator);
  114. builder.Append(trimmedValue);
  115. separator = « , » ;
  116. }
  117. sb.Append(builder.ToString());
  118. sb.Append( « \n » );
  119. }
  120. return sb.ToString();
  121. }
  122. private static List < string > GetHeaderValues( HttpRequestHeaders headers, string headerName)
  123. {
  124. List < string > list = new List < string >();
  125. IEnumerable < string > values = headers.GetValues(headerName);
  126. if (values != null )
  127. {
  128. foreach ( string str in values)
  129. {
  130. list.Add(str.TrimStart( null ));
  131. }
  132. }
  133. return list;
  134. }
  135. private static string GetCanonicalizedResource( Uri address, string accountName)
  136. {
  137. StringBuilder str = new StringBuilder ();
  138. StringBuilder builder = new StringBuilder ( « / » );
  139. builder.Append(accountName);
  140. builder.Append(address.AbsolutePath);
  141. str.Append(builder.ToString());
  142. var param = address.Query;
  143. if ( string .IsNullOrEmpty(param))
  144. return str.ToString();
  145. param = param.Replace( « ? » , «  » );
  146. //List<KeyValuePair<string, string>> values2 = new List<KeyValuePair<string, string>>();
  147. Dictionary < string , string > values2 = new Dictionary < string , string >();
  148. foreach ( string str2 in param.Split( ‘&’ ))
  149. {
  150. List < string > list = new List < string >() { str2.Split( ‘=’ ).Last() };
  151. list.Sort();
  152. StringBuilder builder2 = new StringBuilder ();
  153. foreach ( object obj2 in list)
  154. {
  155. if (builder2.Length > 0)
  156. {
  157. builder2.Append( « , » );
  158. }
  159. builder2.Append(obj2.ToString());
  160. }
  161. values2.Add((str2 == null ) ? str2 : str2.ToLowerInvariant().Split( ‘=’ ).First(), builder2.ToString());
  162. }
  163. List < string > list2 = new List < string >(values2.Select(v => v.Key));
  164. list2.Sort();
  165. foreach ( string str3 in list2)
  166. {
  167. StringBuilder builder3 = new StringBuilder ( string .Empty);
  168. builder3.Append(str3);
  169. builder3.Append( « : » );
  170. builder3.Append(values2[str3]);
  171. str.Append( « \n » );
  172. str.Append(builder3.ToString());
  173. }
  174. return str.ToString();
  175. }
  176. }

 

Cette classe n’est pas complète et ne prend pas en charge toutes les fonctionnalités des services REST, mais elle représente un bon point de départ…

File Pickers UI

Windows 8 met à disposition les classes FileOpenPicker et FileSavePicker. Ces classes permettent depuis une application d’afficher une interface demandant à l’utilisateur de sélectionner un ou plusieurs fichiers dans la cas du FileOpenPicker, et de sélectionner une destination dans le cas du FileSavePicker.

Ces classes sont disponibles dans l’espace de nom Windows.Storage.Pickers.

Envoi de fichiers vers Azure avec le FileOpenPicker

Dans notre application, nous utilisons le FileOpenPicker pour donner la possibilité à l’utilisateur d’envoyer l’un de ses fichiers vers son compte Azure.

La classe FileOpenPicker permet de demander à l’utilisateur de sélectionner un ou plusieurs fichiers. On doit obligatoirement préciser le type de fichier à sélectionner via la propriété FileTypeFilter. Pour supporter tous les types de fichiers, il faut préciser le filtre “*”.

On peut personnaliser le libellé du bouton de validation du picker via la propriété CommitButtonText et on peut également préciser le répertoire de départ via la propriété SuggestedStartLocation.

Pour déclencher l’affichage du picker il faut appeler la méthode PickSingleFileAsync() ou PickMultipleFilesAsync() qui nous retournera les fichiers sélectionnés sous la forme d’objet StorageFile :

  1. private async void Upload_Click( object sender, RoutedEventArgs e)
  2. {
  3. FileOpenPicker fileOpenPicker = new FileOpenPicker ();
  4. fileOpenPicker.FileTypeFilter.Add( « * » );
  5. fileOpenPicker.CommitButtonText = « Upload » ;
  6. fileOpenPicker.SuggestedStartLocation = PickerLocationId .PicturesLibrary;
  7. var files = await fileOpenPicker.PickMultipleFilesAsync();
  8. if (files == null )
  9. return ;
  10. var container = DataContext as StorageItem ;
  11. foreach ( var file in files)
  12. {
  13. // Envoi dans l’Azure Storage
  14. }
  15. }

 

Le résultat de l’appel à la méthode PickMultipleFilesAsync :

image

Téléchargement de fichiers depuis Azure avec le FileSavePicker

Dans notre application le contrôle FileSavePicker est utilisé pour donner la possibilité à l’utilisateur de télécharger vers la destination de son choix un fichier provenant de son compte Azure.

L’utilisation de la classe FileSavePicker nécessite au minimum de spécifier les types de fichiers via la propriété FileTypeChoices.

Comme pour le FileOpenPicker, nous pouvons personnaliser le bouton de validation via la propriété CommitButtonText et spécifier un chemin de départ via la propriété SuggestedStartLocation.

Et enfin nous pouvons suggérer un nom de fichier de destination via la propriété SuggestedFileName :

  1. private async void Download()
  2. {
  3. FileSavePicker savePicker = new FileSavePicker ();
  4. savePicker.SuggestedFileName = blob.Name;
  5. savePicker.CommitButtonText = « Download » ;
  6. savePicker.SuggestedStartLocation = PickerLocationId .PicturesLibrary;
  7. savePicker.FileTypeChoices.Add( Path .GetExtension(blob.Name), new List < string >() { Path .GetExtension(blob.Name) });
  8. var storageFile = await savePicker.PickSaveFileAsync();
  9. if (storageFile != null )
  10. {
  11. // Telechargement du fichier Azure vers le storageFile
  12. }
  13. }

 

L’appel à la méthode PickSaveFileAsync() déclenche l’affichage du picker et nous renvoie un objet StorageFile dans lequel il ne reste plus qu’à écrire nos données :

image

Les contrats File Picker

Pour terminer cet article nous allons implémenter les contrats File Open Picker et File Save Picker. Ces contrats permettent à une application de devenir fournisseur de fichiers dans le cas du File Open Picker Contract et espace de stockage dans le cas du File Save Picker Contract. L’application Skydrive en est un exemple.

File Open Picker Contract

Le contrat File Open Picker est utilisé lorsque l’on veut que notre application devienne fournisseur de fichiers.

Par exemple, lors de la rédaction d’un nouveau mail depuis l’application Courrier, à l’ajout de pièces jointes, l’utilisateur aura la possibilité de sélectionner l’application Azure Explorer pour y sélectionner des fichiers provenant de son compte Azure.

La 1ère étape consiste à déclarer ce contrat dans le manifeste de l’application :

image

Il faut développer une vue permettant à l’utilisateur de sélectionner un fichier. Cette vue sera activée par la classe App en implémentant la méthode OnFileOpenPickerActivated :

  1. protected override void OnFileOpenPickerActivated( FileOpenPickerActivatedEventArgs args)
  2. {
  3. FileOpenPickerPage page = new FileOpenPickerPage ();
  4. page.Activate(args);
  5. }

 

Dans la méthode Activate de la vue, nous récupérons une instance du contrôle FileOpenPickerUI.

On peut personnaliser le titre de la fenêtre via sa propriété Title, récupérer les types de fichier supportés via sa propriété AllowedFileTypes, ou encore être notifier lorsque l’utilisateur supprime un fichier par l’évènement FileRemoved :

  1. public void Activate( FileOpenPickerActivatedEventArgs args)
  2. {
  3. fileOpenPickerUI = args.FileOpenPickerUI;
  4. fileOpenPickerUI.Title = storageAccountName +  » Azure Storage » ;
  5. fileOpenPickerUI.FileRemoved += fileOpenPickerUI_FileRemoved;
  6. Window .Current.Content = this ;
  7. this .OnNavigatedTo( null );
  8. Window .Current.Activate();
  9. }

 

On utilise ensuite la méthode Add(string, IStorageFile) pour ajouter des fichiers à la sélection de l’utilisateur :

  1. private async void GridView_ItemClick( object sender, ItemClickEventArgs e)
  2. {
  3. StorageItem item = e.ClickedItem as StorageItem ;
  4. var blob = await dataSource.DownloadBlob(( Blob )item);
  5. fileOpenPickerUI.AddFile(item.Name, blob);
  6. }

 

Pour tester il suffit de lancer l’application Skydrive, de cliquer sur télécharger et dans le FileOpenPicker sélectionner l’application Azure Explorer :

image

File Save Picker et Cached File Updater Contract

Le contrat File Save Picker est utilisé pour que l’application propose aux autres un espace de stockage. Lors de l’enregistrement d’un fichier via la fenêtre d’enregistrement (FileSavePicker), l’utilisateur peut sélectionner une destination local dans le système de fichiers, ou sélectionner une application qui implémente ce contrat.

Lorsqu’une application va nous demander une destination pour sauvegarder un fichier, nous devrons lui fournir un objet de type StorageFile dans lequel elle écrira les données. Nous allons créer un fichier dans le stockage local de notre application, puis, une fois les données écrites, nous l’enverrons vers Azure.

Dans ce scénario, nous avons besoin d’être notifié lorsque l’application appelante aura terminé d’écrire le fichier. Pour recevoir cette notification nous utiliserons le contrat Cached File Updater.

1ère étape, déclarer les contrats File Save Picker et Cached File Updater dans le manifeste :

 

image

 

Dans la classe App on implémente les méthodes d’activation de nos vues :

  1. protected override void OnFileSavePickerActivated( FileSavePickerActivatedEventArgs args)
  2. {
  3. FileSavePickerPage page = new FileSavePickerPage ();
  4. page.Activate(args);
  5. }
  6. protected override void OnCachedFileUpdaterActivated( CachedFileUpdaterActivatedEventArgs args)
  7. {
  8. CachedFilePage page = new CachedFilePage ();
  9. page.Activate(args);
  10. }

 

Du côté de la vue qui implémente le Save File Picker, on récupère l’instance du FileSavePickerUI et on s’abonne à l’évènement TargetFileRequested :

  1. private FileSavePickerUI fileSavePickerUI = null ;
  2. public void Activate( FileSavePickerActivatedEventArgs args)
  3. {
  4. // cache FileOpenPickerUI
  5. fileSavePickerUI = args.FileSavePickerUI;
  6. fileSavePickerUI.TargetFileRequested += fileSavePickerUI_TargetFileRequested;
  7. Window .Current.Content = this ;
  8. this .OnNavigatedTo( null );
  9. Window .Current.Activate();
  10. }

L’évènement TargetFileRequested est levé lorsque l’utilisateur valide la destination de son fichier. A ce moment là nous devons fournir à l’application appelante un objet StorageFile. Pour donner ce fichier, l’évènement nous envoie un argument de type TargetFileRequestedEventArs. Cet objet possède une propriété Request de type TargetFileRequest, qui lui même a un membre TargetFile de type StorageFile.

L’objet TargetFileRequest possède également une méthode, GetDeferral(), qui nous permet de récupérer un objet de type TargetFileRequestDeferral. Cet objet permet à notre application de s’exécuter de manière asynchrone et elle devra appeler la méthode Complete() pour indiquer à l’application appelante qu’elle a terminé :

  1. var deferral = args.Request.GetDeferral();
  2. var storageFile = await ApplicationData .Current.LocalFolder.CreateFileAsync(sender.FileName, CreationCollisionOption .ReplaceExisting);
  3. args.Request.TargetFile = storageFile;
  4. deferral.Complete();

 

Dans notre cas, nous créons le fichier dans le répertoire local de notre application et nous voulons être notifié lorsque l’application appelante aura écrit les données dans ce fichier, afin de l’envoyer sur Azure. Il faut utiliser la classe CachedFileUpdater et la méthode SetUpdateInformation afin de paramétrer notre notification. Voici le code complet :

  1. async void fileSavePickerUI_TargetFileRequested( FileSavePickerUI sender, TargetFileRequestedEventArgs args)
  2. {
  3. StorageItem storageItem = null ;
  4. await Dispatcher.RunAsync(Windows.UI.Core. CoreDispatcherPriority .Normal, async () =>
  5. {
  6. storageItem = DataContext as StorageItem ;
  7. });
  8. var deferral = args.Request.GetDeferral();
  9. var storageFile = await ApplicationData .Current.LocalFolder.CreateFileAsync(sender.FileName, CreationCollisionOption .ReplaceExisting);
  10. CachedFileUpdater .SetUpdateInformation(storageFile, storageItem.Uri, ReadActivationMode .NotNeeded, WriteActivationMode .AfterWrite, CachedFileOptions .DenyAccessWhenOffline);
  11. args.Request.TargetFile = storageFile;
  12. deferral.Complete();
  13. }

 

Du côté de la vue CachedFilePage, à l’activation de notre page, nous devons récupérer l’objet CachedFileUpdaterUI et s’abonner là l’évènement FileUpdateRequested. Cet évènement sera levé lorsque l’application appelante aura terminé l’écriture du fichier local :

  1. CachedFileUpdaterUI cachedFileUpdaterUI;
  2. internal void Activate(Windows.ApplicationModel.Activation. CachedFileUpdaterActivatedEventArgs args)
  3. {
  4. cachedFileUpdaterUI = args.CachedFileUpdaterUI;
  5. cachedFileUpdaterUI.FileUpdateRequested += cachedFileUpdaterUI_FileUpdateRequested;
  6. Window .Current.Activate();
  7. }

 

Et pour terminer il ne reste plus qu’à télécharger le fichier local vers Azure :

  1. async void cachedFileUpdaterUI_FileUpdateRequested( CachedFileUpdaterUI sender, FileUpdateRequestedEventArgs args)
  2. {
  3. var deferral = args.Request.GetDeferral();
  4. var request = args.Request;
  5. var name = request.File.Name;
  6. var file = await ApplicationData .Current.LocalFolder.GetFileAsync(name);
  7. var uri = request.ContentId;
  8. Uri blobUri = new Uri (uri + « / » + file.Name);
  9. var dataSource = new AzureStorageDataSource (storageAccountName, storageKey);
  10. await dataSource.UploadBlob(uri, file.Name, file);
  11. deferral.Complete();
  12. }

Conclusion

En l’absence de SDK Azure pour Windows 8 ou tout autre technologie, nous pouvons utiliser les services REST pour accéder aux services de stockage. Le code est bien sur un peu plus long à écrire que si nous avions un SDK, mais je ne doute pas que les équipes de développement Azure en fourniront un rapidement.

Nous ne sommes pas entré dans le détail des services REST de l’Azure Storage, mais nous pouvons bien évidemment faire plus : créer/supprimer un containeur, spécifier des permissions, récupérer les métadatas… Pour aller plus loin, je vous invite à consulter la documentation des services REST de l’Azure Storage.

Pour les contrats File Picker et Cached File Updater, je vous conseille cet article MSDN et les différentes ressources qui gravitent autour (Quickstart, Guidelines, Samples).

Concernant le File Save Picker Contract, je n’ai pas trouvé d’autre manière de l’implémenter, conjointement avec le Cached File Updater pour télécharger le fichier sur Azure. Pourtant, lorsque l’on utilise le File Save Picker de l’application Skydrive, on dirait bien que le fichier est téléchargé directement dans le Cloud, de manière synchrone, avant que le picker ne se referme. Si vous avez des pistes à ce sujet, n’hésitez pas!

Laisser un commentaire