Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

[WP7] Upload a file to Dropbox using ReactiveOAuth.WP7

As I was helping @lancewmccarthy on its last application, I ran across the need to upload a file to Dropbox. While there is already a few libraries for WP7 handling this task, I couldn’t get any of them to work as expected: either they missed the file upload feature, or they used an older version of the API that new applications can’t use anymore. As such, I had to dive into the Dropbox’s OAuth API to write my own upload code. It was far from being painless, so I think the whole process is worth sharing.

First, what is OAuth? I won’t give much details as I discovered that recently, but basically it’s a protocol used to authenticate yourself and send orders to an http-based API. On the surface, it’s just about calling the appropriate webpage. But to prevent some kinds of attacks, OAuth forces you to add a few parameters to the uri, like a random value and a dynamically generated signature. It may be just me, but I think the official documentation isn’t precise enough on how to generate the signature, so I quickly gave up and searched for general-purpose OAuth libraries. The one I found and used is ReactiveOAuth.WP7. It was originally designed for Twitter, but it can also be used for Dropbox, with a few changes.

The documentation for Dropbox REST API can be found here. To use it, you must first register your application on the website, to get a unique code that you will use every time you call a Dropbox function. The application registration is done on this page and is almost instant. Note that your application is first registered with ‘testing’ status. It means you can only use your own Dropbox account with it. To remove this limitation, you have to apply for production status, which requires a validation from Dropbox administrators. You just have to fill a description for your app and explain why you need to use Dropbox, then you should receive a confirmation e-mail after a few hours. Anyway, on the application page you’ll find two codes: “App key” and “App secret”. We need these to use the API.

image

Now, let’s start Visual Studio and create a new WP7 project. Add the ReactiveOAuth.WP7 project to your solution. The whole project, not just the binaries, as we’ll have to make a few changes in the code.

In your main project, add a reference to the ReactiveOAuth project. Also add a reference to the Microsoft.Phone.Reactive and System.Observable assemblies.

image

The OAuth authentication and authorization process works as follows: first call the “request_token” page, giving your app key and secret (the two values that were provided on the Dropbox application page). The server answers with a token which will identify your session. Then, the user must allow your application to use his Dropbox account. For security reasons, you cannot automate this part, it requires user interaction. You’ll have to redirect the user to a specific webpage, where he can type his credentials and explicitly allow your application. If the user do theses steps correctly, then a call to the “access_token” page will return a token that you can use to call any API function you want.

For our tests, we need to upload something to Dropbox. For that purpose, we’ll create a test file when the page is loaded, and store it in the isolated storage. Therefore, we create a “CreateDummyFile” function and call it in the “OnNavigatedTo” event:

   1: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
   2: {
   3:     base.OnNavigatedTo(e);
   4:  
   5:     this.CreateDummyFile();
   6: }
   7:  
   8: private void CreateDummyFile()
   9: {
  10:     using (var isolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
  11:     {
  12:         if (isolatedStorage.FileExists("test.txt"))
  13:         {
  14:             isolatedStorage.DeleteFile("test.txt");
  15:         }
  16:  
  17:         using (var stream = isolatedStorage.CreateFile("test.txt"))
  18:         {
  19:             var data = Encoding.UTF8.GetBytes("Hello world! - " + DateTime.Now.ToLongTimeString());
  20:  
  21:             stream.Write(data, 0, data.Length);
  22:         }
  23:     }
  24: }

Then we use the “OAuthAutorizer” class to request a token. “Key” and “Secret” are constants added to the page, containing respectively the app key and app secret.

   1: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
   2: {
   3:     base.OnNavigatedTo(e);
   4:  
   5:     var authorizer = new OAuthAuthorizer(Key, Secret);
   6:  
   7:     authorizer.GetRequestToken("https://api.dropbox.com/1/oauth/request_token");
   8: }

Once we have the token, we must proceed with the authorization. We need to display a webpage to the user, so we add a few UI elements in the XAML:

   1: <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
   2:     <StackPanel>
   3:         <phone:WebBrowser x:Name="WebBrowser" Height="700"  IsScriptEnabled="True"
   4:                       Visibility="Collapsed" 
   5:                           Navigating="WebBrowser_Navigating" />
   6:         <StackPanel x:Name="UploadingPanel">
   7:             <TextBlock Text="Uploading file..." Height="50" TextAlignment="Center" />
   8:             <ProgressBar x:Name="ProgressBar" Height="20" IsIndeterminate="True" Visibility="Visible"/>
   9:         </StackPanel>
  10:     </StackPanel>
  11: </Grid>

In the code, we use the callback of the “GetRequestToken” method to construct the right uri and navigate to this page. The “authorize” function accepts a “oauth_callback” parameter. This parameter tells Dropbox to redirect to a specific page when the authorization process is done. This way we can detect the redirection to know when the user has finished. So our “OnNavigatedTo” event now looks like:

   1: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
   2: {
   3:     base.OnNavigatedTo(e);
   4:  
   5:     this.CreateDummyFile();
   6:  
   7:     var authorizer = new OAuthAuthorizer(Key, Secret);
   8:  
   9:     authorizer.GetRequestToken("https://api.dropbox.com/1/oauth/request_token")
  10:         .ObserveOnDispatcher()
  11:         .Subscribe(token =>
  12:         {
  13:             this.RequestToken = token.Token;
  14:             var url = authorizer.BuildAuthorizeUrl("https://www.dropbox.com/1/oauth/authorize", token.Token);
  15:  
  16:             url += "&oauth_callback=http://dummywebsite/dummy";
  17:  
  18:             this.WebBrowser.Navigate(new Uri(url));
  19:         });
  20: }

Note the call to “ObserveOnDispatcher”. It tells the Reactive extensions to call the callback on the dispatcher thread. It’s required as we need to interact with the UI, to tell the WebBrowser control which page it should display. Without “ObserveOnDispatcher”, we would have to call “Dispatcher.BeginInvoke” to avoid a cross-thread exception.

In the XAML code I’ve provided, the WebBrowser is hidden by default. Now we need to set it to visible, and detect the end of the authorization process to hide it back and request the access token. To do that, we’re going to use the “Navigating” event of the WebBrowser control:

   1: private void WebBrowser_Navigating(object sender, NavigatingEventArgs e)
   2: {
   3:     if (e.Uri.AbsolutePath == "/dummy")
   4:     {
   5:         // Authorization done, cancel the navigation, hide the browser control, and proceed.
   6:         e.Cancel = true;
   7:         this.WebBrowser.Visibility = Visibility.Collapsed;
   8:         this.UploadingPanel.Visibility = Visibility.Visible;
   9:         this.GetAccessToken();
  10:     }
  11:     else
  12:     {
  13:         this.WebBrowser.Visibility = Visibility.Visible;
  14:         this.UploadingPanel.Visibility = Visibility.Collapsed;
  15:     }
  16: }

The “GetAccessToken” method simply request the access token, store it in a property, and call the file uploading code:

   1: private void GetAccessToken()
   2: {
   3:     var authorizer = new OAuthAuthorizer(Key, Secret);
   4:     authorizer.GetAccessToken("https://api.dropbox.com/1/oauth/access_token", this.RequestToken, this.RequestToken.Secret)
   5:         .Subscribe(token =>
   6:         {
   7:             this.AccessToken = token.Token;
   8:  
   9:             this.SendFile();
  10:         });
  11: }

Now comes the tricky part. Out of the box, the ReactiveOAuth library is limited to HTTP GET and POST requests. To upload the file, we’ll need to use HTTP PUT. Therefore, there’s two modifications to do in the library.

First, open the “MethodType.cs” file, and add the PUT method:

   1: using System;
   2:  
   3: namespace Codeplex.OAuth
   4: {
   5:     /// <summary>WebRequest HttpMethodType</summary>
   6:     public enum MethodType
   7:     {
   8:         Get, Post, Put
   9:     }
  10:  
  11:     public static class MethodTypeExtensions
  12:     {
  13:         /// <summary>convert to UPPERCASE string</summary>
  14:         public static string ToUpperString(this MethodType methodType)
  15:         {
  16:             switch (methodType)
  17:             {
  18:                 case MethodType.Get:
  19:                     return "GET";
  20:                 case MethodType.Post:
  21:                     return "POST";
  22:                 case MethodType.Put:
  23:                     return "PUT";
  24:                 default:
  25:                     throw new ArgumentException();
  26:             }
  27:         }
  28:     }
  29: }

Then open the “OAuthClient.cs” file and find the “CreateWebRequest” method. Set its visibility to “Public”, and change the first line from:

   1: var requestUrl = (MethodType == OAuth.MethodType.Get) ? Url + "?" + Parameters.ToQueryParameter() : Url;

to:

   1: var requestUrl = (MethodType == OAuth.MethodType.Get || MethodType == OAuth.MethodType.Put) ? Url + "?" + Parameters.ToQueryParameter() : Url;

What are we doing here? The “files_put” API of Dropbox requires parameters to be provided as for a GET request, and the file data to be provided as POST data. ReactiveOAuth.WP7 doesn’t seem to be able to handle this hybrid query, so we’re bypassing it and exposing the internal WebRequest object to do the upload ourselves. Basically, we only use ReactiveOAuth to create the request and fill all the parameters required by the OAuth protocol, like the signature, then we take car of the uploading.

Now we can write our “SendFile” method, with the necessary upload code:

   1: private void SendFile()
   2: {
   3:     var client = new OAuthClient(Key, Secret, this.AccessToken);
   4:  
   5:     client.Url = "https://api-content.dropbox.com/1/files_put/sandbox/test.txt";
   6:  
   7:     client.Parameters.Add("overwrite", "true");
   8:  
   9:     client.MethodType = MethodType.Put;
  10:  
  11:     var webRequest = client.CreateWebRequest();
  12:  
  13:     webRequest.BeginGetRequestStream(this.StartUpload, webRequest);
  14: }
  15:  
  16: private void StartUpload(IAsyncResult asyncResult)
  17: {
  18:     var request = (HttpWebRequest)asyncResult.AsyncState;
  19:     var postStream = request.EndGetRequestStream(asyncResult);
  20:  
  21:     using (var isolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
  22:     {
  23:         using (var stream = isolatedStorage.OpenFile("test.txt", FileMode.Open))
  24:         {
  25:             stream.CopyTo(postStream);
  26:  
  27:             postStream.Close();
  28:         }
  29:     }
  30:  
  31:     request.BeginGetResponse(this.EndUpload, request);
  32: }
  33:  
  34: private void EndUpload(IAsyncResult asyncResult)
  35: {
  36:     var request = (HttpWebRequest)asyncResult.AsyncState;
  37:  
  38:     try
  39:     {
  40:         var response = (HttpWebResponse)request.EndGetResponse(asyncResult);
  41:  
  42:         response.Dispose();
  43:  
  44:         this.Dispatcher.BeginInvoke(() =>
  45:         {
  46:             this.ProgressBar.Visibility = Visibility.Collapsed;
  47:             MessageBox.Show("Your file has been sucessfully uploaded to Dropbox!");
  48:         });
  49:     }
  50:     catch (Exception ex)
  51:     {
  52:         this.Dispatcher.BeginInvoke(() => MessageBox.Show("An error occured: " + ex.Message));
  53:     }
  54: }

The “StartUpload” method simply opens the file from the isolated storage, then copies the contents inside the web request. The “EndUpload” callback is called when the upload is finished. There, we display a confirmation message.

And we’re done! After executing this program, the Dropbox webpage should appear, asking for your credentials. Then, when you’re done allowing the app, the upload will begin and a message box will confirm you that the file has been uploaded.

image

Note: my code modification inside the ReactiveOAuth.WP7 library is really ugly, as I don’t even bother to use Reactive extensions. That’s because I didn’t really need Reactive extensions in first place, and any ‘general purpose’ OAuth library would do the trick. Still, if you know an elegant way to use Dropbox’s ‘files_put’ function with ReactiveOAuth without modifying the library, please share it in the comments!

Publié dimanche 13 novembre 2011 15:14 par KooKiz
Classé sous : , , ,
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

Pas de commentaires
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Nouveau blog en anglais / New blog in english ! par Le blog de Patrick [MVP SharePoint] le 09-18-2014, 18:42

- [ #Yammer ] From Mailbox to Yammer and back / De votre messagerie vers Yammer et retour ! par Le blog de Patrick [MVP SharePoint] le 09-15-2014, 11:31

- [ #Office 365 ] New service settings panel / Nouveau panneau de paramétrage des services par Le blog de Patrick [MVP SharePoint] le 09-11-2014, 08:50

- Problème de déploiement pour une démo SharePoint/TFS? par Blog de Jérémy Jeanson le 09-10-2014, 21:52

- [ #Office365 ] Delve first impressions / Premières impressions sur Delve par Le blog de Patrick [MVP SharePoint] le 09-09-2014, 16:57

- [ #Office365 ] How to change Administration console language ? / Comment changer la langue de la console d’administration ? par Le blog de Patrick [MVP SharePoint] le 09-09-2014, 08:25

- [ #SharePoint 2013 ] Suppression de bases de données en état “Pas de Réponse” par Le blog de Patrick [MVP SharePoint] le 09-04-2014, 14:10

- Changer l’adresse d’une ferme Office Web Apps associée à SharePoint par Blog de Jérémy Jeanson le 09-01-2014, 22:21

- Une ferme #SharePoint 2013 dans @Azure en quelques clics (1ère partie) ! par Le blog de Patrick [MVP SharePoint] le 08-28-2014, 18:52

- SharePoint 2013: Préparation de la migration - Création des site Templates dans 2010 et 2013 par Blog Technique de Romelard Fabrice le 08-20-2014, 16:31