James O'Neill's Blog

February 3, 2011

How to get files from a Windows Phone 7 App to a PC

Filed under: Uncategorized — jamesone111 @ 12:31 pm

In my review of Windows phone 7 I said that a lot of things I wanted to see changed in future could be summarized as “make it easier to get stuff on and off the phone”. If you are a user of phones I’m afraid this article won’t be of any more than academic interest, but if you write apps for the phone it is something you may want to know, especially if your background does not include Windows Communication Framework (WCF).

An earlier previous post  had said my new Windows 7 phone was Not a “Storage device” – that the Zune software syncs music, pictures and video [only]  without exposing the file system.  The memory card is non removable, and there is no API for developers to upload to Windows live. If you start writing for the phone you quickly find there is an “E-Mail compose task”, but if you does not let you add files from your app as attachments. If you have a small amount of text to send you can can put it in the body of the message. I found that the following code works …

EmailComposeTask ect = new EmailComposeTask();
ect.Subject = "Data from  Windows phone";
using (StreamReader sr = new StreamReader(
               appStorage.OpenFile("myFile.Txt", System.IO.FileMode.Open)))
{
  ect.Body = sr.ReadToEnd();
  ect.Body = ect.Body.Substring(0, 30000);
  }
  ect.Show();

But it’s unsuitable for anything but text and worse, if you want to send more than 32 KB it will crash

image

.NET experts might say “just look at System.Net.Mail”, but the compact framework on the phone doesn’t support the mail parts of System.Net, or the sockets parts (in case you harbour visions of connecting to Port 25 and hand cranking through SMTP – an old party piece of mine), it doesn’t even support ftp. If your app wants to get data in or out it must be over http… 

There are 3rd party storage services on the internet, but very few of them publish an API (Microsoft don’t publish the API for SkyDrive, and most of the other free storage providers follow suit.) As far as can I can find, no one yet has implemented something which can be simply added to an app to give “upload file” functionality. (No sooner will I push the publish button on this post than someone will of). Coding for the that APIs I was able to find would add a lot of complexity but it was also in the back of my mind that, I don’t want people who use my app to be required to sign up with a new storage provider. More to the point I want the data on my PC, not in the cloud so maybe if I could code something to do that … Everything I read said “use WCF” but assumed  pre-existing knowledge beyond mine. what I had.  So this is a crash guide to WCF for one purpose – moving files – and if you are new to WCF you only need to know that:

  • It allows clients to call methods/functions/procedures – “operations” on a server (PC), your work goes into writing the operations, not hooking them up to the network.
  • It works over HTTP so the phone can be a client
  • Access to the sever is via the SOAP protocol, which allows Visual Studio to add “proxy classes” which handle accessing the remote operations, so essentially you say “Make a connection”, “call this”, “call that” without worrying that what you are calling is remote. 

I am going to implement a very simple service, in the simplest possible way. It is going to be a Windows Command Line program, modelled on an example found on MSDN. To set itself up to listen on a particular port it needs to be run as administrator (you can read more about why, and how to avoid this requirement but it is beyond the scope of this post). This means to start debugging it Visual Studio needs to be run as Administrator, and the finished program needs to be Run As Administrator.

The service takes ONE kind of request that contains a file name and text which is to be written to the end of the file (if the file doesn’t already exist, it will be created). That’s it, the following are left as exercises for the reader

  • Any kind of security or authentication. If you leave the server part running and someone accesses it, they can add or change files on your computer.
  • Any handling of “state”,  beyond rudimentary flow control.
  • Handling non-text files.

If you are trying to do this yourself, it can be done in VB (see the MSDN example) but here is a step by step tutorial in C#

  • Start by creating a new project in Visual Studio, use a Windows Console Application  project
  • Add a reference to System.ServiceModel.
  • You should have one file open (program.cs), add to the using statements,
      using System.ServiceModel;
      using System.ServiceModel.Description;
  • as the first lines in your namespace add
    [ServiceContract]
    public interface Itransfer {
             [OperationContract]
            string NewOrAppendString(string fileName, string content);     

    }
  • This defines the service interface clients will be able to connect to and the operations it can perform.
    You can see there is one service and one operation, named NewOrAppendString, which takes two arguments – strings named filename and content, and returns a string as a result.
  • Next add a class which will implement Itransfer. The method(s) just defined must have matching one(s) in this class, like so:
    public class Transfer : Itransfer
        { public string NewOrAppendString(string fileName, string content)
            { try
                { using (var file = System.IO.File.Open(
                      Environment.GetFolderPath(Environment.SpecialFolder.Desktop) +
                       @"\" + fileName, System.IO.FileMode.Append))
                    {using (var sw = new System.IO.StreamWriter(file))
                       {sw.WriteLine(content); }
                    }
                   return "OK";
                }
                catch { return "failed"; }
           }
    }
  • That’s the service defined, notice the file is being saved to the desktop; obviously the program could be more polished. The same applies to the following which will start the service. Visual studio should have give the Program class a main procedure so inside  static void Main(string[] args) we can insert.
    string endpoint      = "http://localhost:31365/Transfer/Service";
    Uri baseAddress      = new Uri(endpoint);
    ServiceHost Selfhost = new ServiceHost(typeof(Transfer), baseAddress);           
    try  {
               Console.WriteLine("Attempting to start listening on " + endpoint);
               Selfhost.AddServiceEndpoint( typeof(Itransfer),
                                            new BasicHttpBinding(), "Transfer");
               ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
               smb.HttpGetEnabled          = true;
               Selfhost.Description.Behaviors.Add(smb);
               Selfhost.Open();
               Console.WriteLine("Press <ENTER> to terminate service.");
               Console.ReadLine();
               Selfhost.Close();
            }
    catch (CommunicationException ce) {
               Console.WriteLine("An Exception occurrred : {0}", ce.Message);
               Selfhost.Abort();
    }

  • This is not hard to decipher, the MetaDataBehavior is set to tell the service to publish its description – without that Visual studio can’t get create the proxy class. I’m using a fixed port and in a polished program we’d let the user specify it, we’d display the IP address to connect to and so on. A “production” version might be more sophisticated in a lot of ways, and obviously you can offer more operations: Want to remotely control a program ? Send a query to a database ? If you can write a method for it, it should be possible (though you should read about the need to serialize data types). Powerful stuff.  
  • The next step is to start the program. Copy the end point to the clipboard and paste it into the address box in Internet explorer . You should get the metadata description page, which tells you a little about the service. Try replacing localhost with the IP address of your computer and test that, then try that URL in IE on your phone / phone emulator.
  • You will probably find that Windows firewall blocks the connection from a phone, but allows it from the emulator (or anything else running on the same PC). If this happens start the Windows Firewall management console, click “Incoming rules” (top left), and then “New Rule” (top right).   Specify “port” in the wizard on the next page select TCP “and enter the port number (31365 in my example code),  select “allow the connection” on the next page, and select all the networks on the one after that and on the last page give the rule a name like “phone transfer”. Unless you can get the page in IE the rest will not work so don’t underestimate the importance of this step.
  • Now you can add a service reference to  project in Visual studio, if you are trying to copy Click for a larger versionmy steps here the simplest way is to create a new phone application. In solution explorer, right click and add a new Service reference. Paste the URL into the address box and press GO. In a moment you should get a message “1 service(s) found”.

    The one service we have is named “transfer” and if you expand that you’ll see that it implements Itransfer which has an operation named NewOrAppendString. You can rename the Namespace. at the bottom I changed mine to “Transfer”

  • With the service reference in place it is possible to define a variable as a member of the phone apps’s main page 
    Transfer.ItransferClient client;
  • Then I use it in some code attached to a button in my phone app
    string addrText = http://192.168.1.100:31365/Transfer/Service/Transfer"; 
    client = new Transfer.ItransferClient("BasicHttpBinding_Itransfer", addrText );
    client.NewOrAppendStringCompleted +=
                 new  EventHandler< Transfer.NewOrAppendStringCompletedEventArgs >
                        (client_NewOrAppendStringCompleted);
    client.CloseCompleted +=
                 new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>
                        (client_CloseCompleted);
    String mytext = "here is some text" + Environment.NewLine ; client.NewOrAppendStringAsync("Test.txt", mytext );
    client.CloseAsync;
  • I’m changing the address of the service from “localhost” to my machine’s IP address, and normally we’d have a mechanism for this information to be put into the program. The name that’s used in the next line is a bit hard to discover , it is the address specified when the service reference was added ( http://192.168.1.100:31365/Transfer/Service/ ), with service name (Transfer) appended to the end. If you look back at the console program you can see where that name is specified.  BasicHttpBinding was specified for the end point in that program too, and the end point name is the binding name joined to the interface name with an underscore.
  • Client has a method to call NewOrAppendString asynchronously. It will then raise an event to say the call has completed (the same applies to closing the client connection), so we add a couple of procedures to handle these events, for now they can use MessageBox.Show to say they have been called.
  • At this point it is necessary to break the app. Stop the service, then run the app and click the button and you will get an error in the code generated by adding the service reference.
    public string EndNewOrAppendString(System.IAsyncResult result) {   
        object[] _args = new object[0];
        string _result = ((string)(base.EndInvoke("NewOrAppendString", _args, result)));
        return _result;}
  • The simplest thing is to wrap a try , catch round this so that a failure does not cause the program to die (rather charmingly the phone tidies up after a crash so you don’t see the error, the program just goes away). We can look at the result in the event handler and see if we had an error. 
    public string EndNewOrAppendString(System.IAsyncResult result) {
        object[] _args = new object[0];
        string _result = "";
        try { _result = ((string)   <etc as above> ;}
        catch { }
        return _result;
    }
  • Now if you run the app again the call will come back without crashing and you can look for success and assume failure if you don’t explicitly get success.. So now you can start the command line program and try again. If all is well a file should appear on your desktop .
  • This is all grand, but you’ll find when you want to send files that there is a limit to how big the request can be: I send my data when it exceeds 4000 bytes to remain inside this limit. You can also out for yourself (by modifying this code to send the numbers from 1 to 1000 as separate requests) there  is no guarantee that requests will be processed in the order they are sent.  My solution is to only send the next block of a file when I have had the OK returned from the current one.  I define a string , transferFileName, and a stream reader, transferFileReader at the Page level in the phone app along with transferClient then I have the following code for my button
    private void btnSend_Click(object sender, RoutedEventArgs e){
        transferFileName    = filesList.SelectedItem.ToString();
        transferFileReader  = new StreamReader(appStorage.OpenFile(transferFileName ,
                                                       System.IO.FileMode.Open));
        string myText        = transferFileReader.ReadLine();
        string addrText      ="http://192.168.1.100:31365/Transfer/Service/Transfer";
       transferClient          = new Transfer.ItransferClient("BasicHttpBinding_Itransfer",
                                                       addrText);
       client.NewOrAppendStringCompleted +=
                 new  EventHandler< Transfer.NewOrAppendStringCompletedEventArgs >
                        (client_NewOrAppendStringCompleted);
       client.CloseCompleted +=
                 new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>
                        (client_CloseCompleted);
        transferClient.NewOrAppendStringAsync(transferFileName , myText);
    }
  • This is much the same as before except I’m reading from a file and the name for the destination and reader object are accessible to other methods. Astute readers will also have spotted that I only send one line of the file. That’s it. Nothing can happen until the completed event fires and its method gets called, which looks like this :
    void client_NewOrAppendStringCompleted(object sender,
                                         NewOrAppendStringCompletedEventArgs e)
    {
        if (e.Result.ToUpper() == "OK")
            {  string myText = "";
                while ((myText.Length < 4000) && (!transferFileReader.EndOfStream))
                    { myText += transferFileReader.ReadLine() + Environment.NewLine;}
                if (myText != "")
                   { transferClient.NewOrAppendStringAsync(transferFileName, myText); }
            }
        if ((transferFileReader.EndOfStream) | (e.Result.ToUpper() != "OK"))
            {  transferClient.CloseAsync(); }
        if (e.Result.ToUpper() != "OK")
            { var foo = MessageBox.Show("An error occurred in the transfer"); }
    }

    void client_CloseCompleted(object sender,
           System.ComponentModel.AsyncCompletedEventArgs e)
    {  transferFileReader.Close(); }

  • So if the result is “OK” we read from the file until we have 4K of data or hit the end of the file. We send the text if there is any (we’ll immediately hit the end of file if we got the OK back from the very last block in the file, and we want to avoid an infinite loop). We client if we got to the end, or hit an error. And if we hit an error we say so, and when the client closes, we close the file. Even with a couple of extra bits for specifying the port and server name it comes out at only about 60 lines of C# It takes me 50-something lines to handle input of server name and port and persist them between settings. 

Eventually I’ll write a nicer and more versatile version, but as I’ve been at pains to point out, my purpose is to show how this stuff works anyone going through the same thing can learn from what I went through.

Advertisements

3 Comments

  1. So, did you find any easy way to get files from the phone?
    I want to write, say “music generation application” that will generate wav files. How I will let the user to retrieve them from my phone?
    Any help would be welcome.

    Comment by Evgeny Vinnik — February 9, 2011 @ 9:02 pm

  2. This is the best I’ve got so far I’m afraid.

    Comment by jamesone111 — February 9, 2011 @ 10:52 pm

  3. Well, I’ve already found reasonable solution.
    I’ve decided to implement Dropbox support into my app.

    I’ve found working library on the Codeplex http://wp7dropbox.codeplex.com/
    and contacted to its developer, he promised me full support if I will have some problems.

    This solution is real alternative to custom WCF solutions.

    Comment by Evgeny Vinnik — February 9, 2011 @ 10:56 pm


RSS feed for comments on this post.

Blog at WordPress.com.

%d bloggers like this: