Export (0) Print
Expand All

Implementing Always-Up-To-Date Functionality in the .NET Compact Framework by Using Web Services

.NET Compact Framework 1.0
 

Alex Yakhnin
IntelliProg, Inc.

September 2005

Applies to:
   Microsoft Exchange Server 2003
   Microsoft .NET Compact Framework
   Microsoft Windows CE .NET
   Windows Mobile–based devices

Summary: Find out how to implement always-up-to-date (AUTD) data synchronization functionality in the .NET Compact Framework by creating the generic ServiceWatcher class that could be used for monitoring any custom type of Web service. (11 printed pages)


Download AUTD_Functionality_NETCF_WebServices.msi from the Microsoft Download Center.

Contents

Introduction
AUTD: Push or Smart Pull?
The ServiceWatcher Class
Conclusion

Introduction

Windows Mobile–based devices are connected to the network for longer lengths of time—sometimes continually. This situation often requires the data on the devices to be synchronized with the server in a timely manner.

After Microsoft made the recent announcements about the upcoming Microsoft Exchange Server 2003 Service Pack 2, which will bring "new seamless direct push e-mail experience" among other features, a question came to mind, "How were the Exchange team and the Windows Mobile group able to implement this experience?". The answer came quickly because one of the members of the Exchange team posted a detailed explanation on a blog about how the teams achieved the required always-up-to-date (AUTD) functionality.

AUTD: Push or Smart Pull?

According to the blog, the Exchange team came up with the following solution:

  • The device issues a long-lived HTTP request to the Exchange server, which asks Exchange to report any changes that occur in the mailbox within a specified time limit.
  • Upon receiving this request, the server will monitor the appropriate folders until either the time limit expires or a change occurs in one of these folders.
  • When the device receives a non-empty response, it issues a synchronization request against the data it expects to receive.
  • In the case when a response is empty, the device just re-issues the long-lived request for change notification, which is called heartbeat.

To implement this solution on the Microsoft .NET Compact Framework, you can employ Web services to provide the functionality that is provided by the Exchange server.

The ServiceWatcher Class

To implement AUTD for applications built with the .NET Compact Framework, you can create the ServiceWatcher class, which will have these important tasks:

  • Make a long-lived heartbeat Web service request on a separate thread
  • Raise the event to the client upon receiving a valid response from the server
  • Handle possible time-outs and disconnects
  • Be generic enough to handle any type of Web service proxy

The following code example shows how to create the ServiceWatcher class and to declare its constructor.

public class ServiceWatcher 
{    
    #region fields

    private SoapHttpClientProtocol service;
    private int retryInterval;
    private int timeout;
    private bool stop  = false;
    private MethodInfo methodInfo;
    private object[] parameters;

    #endregion // fields

    #region constructors

    public ServiceWatcher(SoapHttpClientProtocol service, int retryInterval)
    {    
        this.timeout = timeout;
        this.retryInterval = retryInterval;
        this.service = service;
        this.service.Timeout = timeout;
    }
    #endregion // constructors 

The constructor accepts a few important parameters. The first parameter is an instance of the Web service proxy class that was declared as a type of SoapHttpClientProtocol. Because every generated Web service proxy must be derived from this type, SoapHttpClientProtocol allows the passing of any Web service proxy implementation that you want. The other parameter in the constructor defines the time interval in milliseconds for the Web service sleep time before the device attempts to connect after it receives an unexpected disconnect.

To call a method on the Web service proxy if you don't know its concrete type, you can use reflection to obtain a MethodInfo instance. Then, you can use this instance to invoke the required Web service method. The following code example shows the Start method that you need to add to the ServiceWatcher class.

/// <summary>
/// Starts calling the specified Web service method
/// </summary>
/// <param name="methodName">The name of the method</param>
/// <param name="parameters">Parameters for Web service method</param>
public void Start(string methodName, object[] parameters)
{
    // Get MethodInfo through reflection
    this.methodInfo = this.service.GetType().GetMethod(methodName);
    this.parameters = parameters;
    // Start the worker thread
    ThreadStart threadStart = new ThreadStart(WorkerProcess);
    Thread thread = new Thread(threadStart);
    thread.Start();
}

In addition to obtaining an instance of the MethodInfo class, the Start method will produce a separate worker thread that will make the required Web service call, as shown in the following code example.

private void WorkerProcess()
{
    while(!stop)
    {
        if (!InvokeService())                
        {
            // Disconnected before time-out expired
            // Sleep and try again
            OnStatusEvent("Sleeping before re-trying...");
            Thread.Sleep(retryInterval);
            OnStatusEvent("Re-trying to connect...");
        }
    }
}

private bool InvokeService()
{
    object result = null;
    try
    {
        // Call Web service method
        result = methodInfo.Invoke(service, parameters);
    }
    catch(System.Net.WebException ex)
    {
        // Disconnected or timed out
        OnStatusEvent(ex.Message);
        return false;
    }
    catch(Exception ex)
    {
        // Some other exception
        OnStatusEvent(ex.Message);
        return false;
    }
    if (result != "")
    {
        OnDataChanged(result);
    }

    return true;
}

You should be able to understand from the previous code that the WorkerProcess method makes continuous calls to the helper InvokeService method. This method makes synchronous Web service calls and raises the DataChanged event when a non-empty response is received from the server. The return status true or false from the InvokeService method indicates to the WorkerProcess method if it needs to reissue a heartbeat immediately or to wait an already defined interval of time (which is determined by retryInterval) in case of disconnect or failure. The code also raises another event, StatusEvent, that notifies the client of the current status.

The following code example shows how to declare appropriate delegate, event types, and helper methods.

public delegate void DataChangedEvent(object dataKey);

public event DataChangedEvent DataChanged; 
public event DataChangedEvent StatusEvent;

private void OnDataChanged(object data)
{
    if (DataChanged != null)
    {
        DataChanged(data);
    }
}

private void OnStatusEvent(object data)
{
    if (StatusEvent != null)
    {
        StatusEvent(data);
    }
}

ServiceWatcher Class Code Example for the Server

The following code example illustrates how to use the ServiceWatcher class to enable a server to provide up-to-date content of a text file that is located on the server to a Windows Mobile client. So to proceed with the plan, you should create a Web service with the following method.

[WebMethod]
public string Listen(string user, string password, int timeout)
{
    // In the real life scenario you'd authenticate the client
    // Wait for the file to be updated
    if (dataWatcher.Wait(timeout))
{
        // Send the file name
        return dataWatcher.FileName;
    }
    else
    {
        // Send empty response
        return String.Empty;
}
}

The Listen method makes a synchronous call to the DataWatcher class Wait method.

Note   Keep in mind that because the Listen Web method is synchronous, the server uses the thread for as long as it waits for data changes to occur, thereby burning a lot of resources. One way to avoid using up all of the threads in the server pool is to use server-side asynchronous Web methods.

The DataWatcher class was created with a single goal: watch for the changes against a data file and return from the Wait method call when the change is detected. To provide the detection of the file updates, the DataWatcher class is using the System.IO.FileSystemWatcher class and its ability to recognize any updates to the file system. The following is a code example for the DataWatcher class that shows its private fields and the constructor.

public class DataWatcher
{
    #region fields

    private FileSystemWatcher fileWatcher;
    private ManualResetEvent waitEvent;
    private string latestData = "";
    private string fileName  = "dataFile.txt";
private bool dataChanged = false;

    #endregion

    // Constructor
    public DataWatcher(string path)
    {
        waitEvent = new ManualResetEvent(false);
        // Initialize FileSystemWatcher
        fileWatcher = new FileSystemWatcher(path, "*.txt");
        fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
        //  Hook up into Changed event
        fileWatcher.Changed+=new FileSystemEventHandler(fileWatcher_Changed);
        // Start listening for changes
        fileWatcher.EnableRaisingEvents = true;
    }

}

The following code example shows how the DataWatcher class uses the System.IO.FileSystemWatcher class to connect to its Changed event.

private void fileWatcher_Changed(object sender, FileSystemEventArgs e)
{
    if (e.ChangeType == WatcherChangeTypes.Changed)
    {
            
        if (fileName.ToLower() == e.Name.ToLower())
        {
            // The changes have been acquired
            waitEvent.Set();
        }
    }
}

The following code example shows the Wait method.

public bool Wait(int timeout)
{
    dataChanged = false;
    waitEvent.Reset();
    waitEvent.WaitOne(timeout, true);
    return dataChanged;
}

This method waits for the waitEvent of the ManualResetEvent type to be signaled from the fileWatcher_Changed event handler. I have created a plain text file dataFile.txt and copied it into the virtual directory of the Web service. This file is going to play the role of a data resource that FileSystemWatcher will be monitoring.

The following code example shows how to add another method to the Web service that returns the content of the file when the client requests it.

[WebMethod]
public string GetData(string key)
{
    // Read the content of the file
StreamReader sr = new StreamReader(HttpRuntime.AppDomainAppPath + "\\" + key);
    string data = sr.ReadToEnd();
    sr.Close();
    return data;
}

ServiceWatcher Class Code Example for the Client

This section shows a Pocket PC client application that makes use of the ServiceWatcher class and the resulting Web service. The client will consist of a single Microsoft Windows Form, a few buttons, and a text box for displaying the file content, as shown in Figure 1.

Figure 1. The client form layout

The following code example shows the event handler of the Start button. It shows how to create an instance of the Web service, retrieve the current context data from the file on the server, and then construct an instance of the ServiceWatcher class.

private void cmdStart_Click(object sender, System.EventArgs e)
{
    service = new Service();
    // Get the current data
    txtData.Text = service.GetData(keyData);
    // Create an instance of the ServiceWatcher
    msgWatcher = new ServiceWatcher(service, 20000);
    msgWatcher.DataChanged += new DataChangedEvent(msgWatcher_DataChanged);
    msgWatcher.StatusEvent += new DataChangedEvent(msgWatcher_StatusEvent);
    msgWatcher.Start("Listen", new object[]{"alex", "password", 300000});
    lblStatus.Text = "Waiting for updated data.";
}

The code also connects to the class's DataChanged and StatusEvent events and makes a call to the Start method of the newly backed ServiceWatcher class by passing the name of the Web service method and the required parameters. The following code example shows the event handlers for DataChanged and StatusEvent events. Because these events will be raised from a different from UI thread, you need to use the Control.Invoke method call to update controls on the form, as shown in the following code example.

private void msgWatcher_DataChanged(object key)
{
    this.keyData = (string)key;
    this.Invoke(new EventHandler(UpdateTextBox));
}

private void msgWatcher_StatusEvent(object key)
{
    this.status = (string)key;
    this.Invoke(new EventHandler(UpdateStatus));
}
private void UpdateTextBox(object sender, System.EventArgs e)
{
    lblStatus.Text = "Retrieving the data...";
    txtData.Text = service.GetData(keyData.ToString());
    lblStatus.Text = "Waiting for updated data.";
}

private void UpdateStatus(object sender, System.EventArgs e)
{
    lblStatus.Text = this.status;
}

Now, the ServiceWatcher class can notify the client of possible changes to the data file. To test this functionality, open the dataFile.txt on the server by using Notepad, modify its contents, and save the file. The result should appear immediately on the screen, as shown in Figure 2.

Figure 2. The data has been updated

Conclusion

In this article, you can create a framework that opens up unlimited usage possibilities for creating AUTD functionality for .NET Compact Framework applications. Instead of listening for updates of the text file, you can provide your own implementation of the DataWatcher class on the server that would notify the client of changes in the database, message queue, or through another Web service. However, the code sample of the Web service in this article is simplified, and it doesn't handle distinctions among different clients. Also keep in mind that each device could be holding a connection open for a long time, and by default ASP.NET will allow only 20 concurrent threads. As the number of devices increases, the number of available threads will get used up quickly. This will also affect other Web-based applications running on the same IIS Web server. Don't forget to make the appropriate modifications. Despite these limitations, you should be able to extend the code to serve your needs.

Show:
© 2014 Microsoft