Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Windows Phone 7, Android and iOS with Mono IV: Webservices

In this series so far:
- Mono I- Getting Started
- Mono II- Basic Navigation
- Mono III: Shared Libraries

Now that you’ve got a simple application up and running it’s time to think about accessing data. We’re going to jump in the deep end and look at how to consume a web service. In this case I’ve created a vanilla WCF service with the following definition (just using the WCF Service Application template in Visual Studio):

[ServiceContract]
public interface IService1 {    
    [OperationContract]
    string GetData(int value);
}
 

Note I: I’ve created this service in a new solution in an instance of Visual Studio 2010 running as administrator. I did this because I wanted to run the service in IIS so that it can be accessed from multiple computers (so that the iphone/simulator on my Mac can access it). See the following image to configure the service to run on IIS.

image

Note II: Because I’m running this service in IIS 7 with .NET 4 there is almost no configuration for this service in the web.config file. What this does mean is that because I’m publishing this service over HTTP the default binding is the basicHttpBinding. As with Silverlight and the .NET Compact Framework, there is only support for basicHttpBinding (or the webHttpBinding if you want to roll your own web requests).

 

Windows Phone 7

Adding this service to your Windows Phone 7 application is unbelievably simple. However, to make it slightly more complex we’re going to add the service into our shared library. This way we can have our service logic in a common place across all three platforms.

- Right-click the shared library (BuiltToRoam.General) and select Add Service Reference

- Enter the address of the wcf service (eg http://localhost/SimpleService/Service1.svc” and hit Go. This should return the service information. Give it a namespace (eg Services) and click OK.

image

This will generate a folder called Service References, a number of files that contain the proxy classes for the referenced service and a ServiceReferences.ClientConfig file, which includes endpoint configuration information about the service.

- Add the following code to the MainPage.xaml.cs code behind file.

Service1Client client=new Service1Client(); 
public MainPage() {
     InitializeComponent();
     client.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>(client_GetDataCompleted);
     client.GetDataAsync(5);
}

void client_GetDataCompleted(object sender, GetDataCompletedEventArgs e) {
     var result = e.Result;
}

Put a breakpoint on the last line of the above code and run the application. What you should notice is that the application will fail throwing an exception. This is because we haven’t told the application the endpoint or binding to use for the service request. Normally this would be specified in the ServiceReferences.ClientConfig file which would be automatically added to the application when adding the service reference. However in this case because we added the service reference to a class library we need to make sure we include the ClientConfig file in the application.

- Right-Click the WP7 application project node (ie BuiltToRoam) and select Add >Existing Item. Navigate to the shared library (ie BuiltToRoam.General) and select the ServiceReferences.ClientConfig file. Do NOT click the centre of the Add button. Instead click the down arrow on the right side of the Add button. Then select Add as Link.

image

Note: This is important because if we update the service reference in the future we don’t want to have to remember to update additional copies of this file.

- F5 to build and run – this should run smoothly (if not, make sure IIS and the service application are running smoothly).

 

iOS

From the iOS application to call our wcf service we can reuse the proxy classes that we just generated.

- Right-click on the shared library (BuiltToRoam.General.iOS) and select Add > Add Files. Select all the files in the Service References\Services folder and click Open. This will add the proxy files to the project with the appropriate build actions.

- Check that the shared library builds – I had to add a reference to the System.ServiceModel assembly at this point (right-click References and select Edit References; from the Packages tab select System.ServiceModel and click OK). You will probably want to add this assembly and System.Xml.Linq to the References for the iOS Application project too.

- Right-click the iOS application project and select Add > Add Files. Locate the ServiceReferences.ClientConfig file that is situated in the shared library project folder. Click Open to add this file – this will display a dialog prompting you to add as a Link or Copy the file. You want to ensure we only have a single copy of this file, so select the Link option

- You need to set the Build Action of the ServiceReferences file you’ve just linked to Content. Do this by right-clicking the file and setting the Build Action directly in the context menu.

 

Screen shot 2011-04-08 at 12.01.01 PM

- Now, all you need to do is write some code to read the configuration file, setup the proxy client and call the service.

Service1Client client;
public MainPageController (IntPtr p) : base (p) {
    EndpointAddress endpoint;
    var name = Environment.CurrentDirectory + "/ServiceReferences.ClientConfig";
    using(var file = System.IO.File.Open(name,System.IO.FileMode.Open,System.IO.FileAccess.Read))
    using(var reader = new System.IO.StreamReader(file)) {
        var xml =XElement.Load(reader);
        endpoint = (from ep in xml.Descendants("endpoint")
                             select new EndpointAddress(ep.Attribute("address").Value)).FirstOrDefault();
    }
    client = new Service1Client(new BasicHttpBinding(),endpoint);
    client.GetDataCompleted+=new EventHandler<GetDataCompletedEventArgs>(client_GetDataCompleted);
    client.GetDataAsync(5);  
}

private void client_GetDataCompleted(object sender, GetDataCompletedEventArgs e) {
    var result = e.Result;
}

Note: The Environment.CurrentDirectory is a reference to the location on disk where Content files get copied to when the application gets installed on the device. From there you can open it and use a bit of Xml parsing to read out the endpoint configuration value

Set a break point in the client_GetDataCompleted method to confirm the service call works

 

Android

To make use of the same wcf proxy classes we generated earlier we need to a) add the appropriate files to the shared library and b) add the service configuration to the Android application project.

- The first step is relatively straight forward: In Solution Explorer select the shared project (BuiltToRoam.General.Android) and toggle the Show All Files button in the toolbar of the Solution Explorer window, so that files that aren’t currently part of the project are shown.

- Right-click on the Service References folder and select Include in Project. You can now toggle Show All Files to hide the excluded files again. You should end up with a structure in the shared library similar to the following.

image

The Android application project should already have a reference to the shared library from what we did in the previous posts. However, it’s missing the wcf service configuration file, ServiceReferences.ClientConfig. Unfortunately you can’t just add this to the project and expect it to work, which is what we did in the wp7 case. It’s a little more involved…

- Right-click the shared library project and select Properties. Go to the Build Events tab and add the following Post-Build event:

xcopy "$(ProjectDir)ServiceReferences.ClientConfig" "$(ProjectDir)..\..\NicksNetTravelsAndroid\NicksNetTravelsAndroid\Resources\Raw\" /y

To break this down the Post-Build event commands will get run after this project has been built. In this case we’re copying the ServiceReferences file from the shared library project directory to the Resources\Raw sub-folder of the Android application project directory.

- Build the solution, and then toggle the Show All Files option for the Android application project. This should reveal the ServiceReferences.ClientConfig file within the Raw directory. If an error is thrown during the build to do with the xcopy command, you may have either got the path wrong (source or destination), or the Raw folder may not exist, in which case you need to create it.

- Right-click the ServiceReferences.ClientConfig file and select Include In Project. Make sure that the Build Action (in the Properties window) is set to AndroidResource

This has included this file as a raw resource that can be accessed from within the Android application. Now in the body of Activity1 we’ll need to open this resource, read out the wcf service configuration which we can use when creating the instance of the proxy.

Service1Client client;
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);

EndpointAddress endpoint;
using (var inputStream = Resources.OpenRawResource(Resource.Raw.ServiceReferences))
using(var reader = new StreamReader(inputStream))
{
var xml = XElement.Load( inputStream));

endpoint = (from ep in xml.Descendants("endpoint")
select new EndpointAddress(ep.Attribute("address").Value)).FirstOrDefault();
}
client = new Service1Client(new BasicHttpBinding(), endpoint);
client.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>(client_GetDataCompleted);
client.GetDataAsync(5);
}


void client_GetDataCompleted(object sender, GetDataCompletedEventArgs e)
{
var result = e.Result;
}
 

We could have just created the EndpointAddress in code but that would mean that we’ve got it hard coded within the application. By reading the contents of the ServiceReferences file we’re at least keeping the wcf service configuration information in a single file for all three platforms.

- Set a breakpoint in the client_GetDataCompleted method to see the result returned from the service.

Pingbacks and trackbacks (2)+

Comments are closed
Windows Phone 7: Tombstone Frustration

Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Windows Phone 7: Tombstone Frustration

Peter Torr has opened a can of worms by requesting feedback on Tombstoning within Windows Phone 7. Conceptually this isn’t a particularly difficult concept – when your application goes into the background there is a chance that the operating system will terminate the process in order to reclaim system resources. Unfortunately the current implementation in Windows Phone 7 has lead to a lot of confusion. Developers don’t know when their application is going to be terminated, restarted, suspended or resumed. As such they’re using trial and error to attempt to predict when their application is going to be terminated. Let’s start with a couple of examples to illustrate this point.

Scenario 1: Application is not terminated
- User starts an application
- User clicks the start button
- User clicks the back button, returning to the same instance of the application

Scenario 2: Application is terminated (tombstoned) and then restarted
- User starts an application
- User clicks the start button
- User clicks on another application (this will terminate the running instance of the first application)
- User clicks the back button (this will terminate the second application, returning the user to Start)
- User clicks the back button again (this will launch a new instance of the first application)

At the moment the behaviour of WP7 is fairly deterministic, which means that with enough trial and error you can determine all of the scenarios under which your application will be tombstoned or not. However, the premise of the model is that your application could be tombstoned at any stage whilst it is in the background to allow the operaing system to reclaim resources. I think it was one of the earliest CTPs of WP7 that actually behaved this way and it was only in the beta where a more deterministic model was imposed. The upshot of this is that you need to anticipate that your application will be tombstoned when it goes into the background. This is NOT to say that you should assume it has been tombstoned, you just need to code for the case where it is tombstoned.

Ok, so how do we deal with tombstoning…. Again, Peter Torr has a couple of things to say about this in his post on handling activating and deactivating events. In this post Peter discusses the different sequence of events that happens when an application is tombstoned v’s the sequence that happens when it is not. Let me reproduce them again here for your pleasure:

Tombstone Case (typical)

  1. Current page gets OnNavigatedFrom
  2. Application gets Deactivated
  3. Process dies
  4. Process starts
  5. Application gets constructed
  6. Application gets Activated
  7. Current page gets constructed
  8. Current page gets OnNavigatedTo

Non-Tombstone Case (Start -> Back)

  1. Current page gets OnNavigatedFrom
  2. Application gets Deactivated
  3. Application gets Activated
  4. Current page gets OnNavigatedTo

Now, since you want to be able to handle both scenarios it would make sense to only work with events/method calls that happen in both cases. The intersection of these two scenarios actually coincides with the Non-tombstone case where there are OnNavigatedFrom, Deactivated, Activated and OnNavigatedTo events/methods. These are the points where you should be doing ALL transient data persistence and restoration.

“Transient data”….. please define! Ok, I typically think of two different types of data within an application. There is persistent data, which is any data the user has saved or that should survive multiple instances of an app (for example a document that the user saves would be persistent data because they’d expect it to be there the next time they run the app). Then there is transient data, which is any information that the user has entered but hasn’t been saved (for example form fields that have been completed but the form hasn’t been submitted). The user would expect transient information to survive through the lifetime of the page. By this I mean that if they navigate off to another application and then return to the original app the transient data would still be on the page. Conversely if they close the page by navigating using the Back button, they’d expect that data to be lost. Similarly if they restarted the app from the Start they would not expect the data to still be there.

So this bodes the question, how do we persist transient data? If we saved it to isolated storage then it’s going to behave like persistent data. Luckily Windows Phone 7 has a solution in the form of both application and page level state dictionaries. When an application is launched these dictionaries are empty. You can populate these dictionaries with data (key value pairs) and the data will survive tombstoning. The only difference between the application level dictionary and the page level dictionaries are that the page dictionaries only last the lifecycle of the page. When the use clicks the back button to close a page, the associated state dictionary is destroyed.

Ok, so that’s starting to make sense, but where’s the best place to save transient state? The answer to this is based on the lifecycle of the data itself. You may have data that is used application wide, in this case you’ll want to persist and restore this data in the application state dictionary in the Deactivated and Activated events. Alternatively, any data that is page specific should be persisted and restored in the state dictionary for that page in the OnNavigatedFrom and OnNavigatedTo methods.

Example time…..

Application Wide Transient State

public string ApplicationWideData { get; set; }
private void Application_Activated(object sender, ActivatedEventArgs e)
{
    object stateData;
    if (PhoneApplicationService.Current.State.TryGetValue("AppWideData", out stateData))
    {
        ApplicationWideData = stateData as string;
    }
}

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
    Debug.WriteLine("Application Deactivated");
    PhoneApplicationService.Current.State["AppWideData"] = ApplicationWideData;
}

 

Page Transient State

protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);

    this.State["PageData"] = PageData;

    Debug.WriteLine("(Main Page) Navigated From");
}

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    Debug.WriteLine("(Main Page) Navigated To");

    object stateData;
    if (this.State.TryGetValue("PageData", out stateData))
    {
        PageData = stateData as string;
    }
}

In most cases you’re going to want to persist transient information about the page. You should develop pages so that they don’t rely on any page being created or loaded prior to them. Doing this, and making use of the page state dictionary, is by far the easiest way to handle the challenges associated with tombstoning.

Comments (2) -

  • Adrian Tsai

    7/03/2011 10:08:12 PM |

    I just wanted to make a comment about scenario 1: I don't know if this is a difference between Silverlight and XNA or if we have different meanings of "terminated", but I'm fairly certain that if the user presses the start button, the current application is always terminated.

    By that I mean that the application stops execution and is unloaded from memory completely. When the user presses the back button to return to the application, the application is started again from the beginning - constructors and all. That means that when the user presses the start button, the application is always tombstoned and then the process is killed.

  • Nick

    8/03/2011 2:17:39 AM |

    As far as I know scenario applies to SL and XNA (although could be wrong about XNA since I haven't done much with it).

Pingbacks and trackbacks (2)+

Comments are closed