June 2010

Volume 25 Number 06

Silverlight Online - Silverlight in an Occasionally Connected World

By Mark Bloodworth | June 2010

People live in an online world—or at least some of us do, some of the time. At some point in the future, there may be pervasive, always-on connectivity with more bandwidth than necessary, but not today. In reality, we are occasionally connected, occasionally with enough bandwidth. Rarely do we know which state we are in at a given time.

In order to design applications that can deliver the best user experience in this reality, there are numerous architectural choices to consider.

Smart clients, rich and poor alike, share a common attribute in that they’re deployed on a local machine. As such, it’s inherently possible to run these applications without being connected to a network. A traditional browser-based application, on the other hand, must be connected to its remote Web server in order to run.

In between these two extremes exists an ever-increasing range of options. All offer varying abilities to run an application offline, along with varying degrees of flexibility and interactivity for UI design, and they impose varying levels of security restrictions. We will discuss the latest incarnation of occasionally connected applications, which come with a highly interactive user experience and can run either inside or outside of a browser. We’ll present code samples that deal with network connectivity detection, along with background workers that upload and download data when online.

Context

Let’s imagine the evolution of a typical application relevant to this discussion. Our example started as a simple, thick-client application that ran on Windows operating systems only. Though it allowed users to work offline, limitations of the initial solution became increasingly apparent:

  • There was a requirement added to support multiple operating systems. Only a subset of potential users could be supported in the first version.
  • Deployment issues caused discrepancies in the versions installed by the user base.

With increased pressure to support a lighter-weight solution that worked across multiple operating systems and minimized deployment issues, the application was rewritten as a simple, thin-client, HTML application. However, this led to another set of issues:

  • Its UI capabilities were limited, resulting in a less-than-intuitive experience.
  • It required lengthy browser-compatibility testing.
  • Performance over many users’ networking infrastructures was poor. For example, large quantities of reference data had to be downloaded each time a user needed to fill out a form, along with extensive scripts to cater to the logic involved in validation.
  • Users couldn’t use the application offline.

Clearly, that version didn’t make the grade either.

The ideal yet elusive solution in this case is a Rich Internet Application (RIA) with an intuitive and flexible UI. The application needs to let users manage large quantities of data and perform asynchronous data uploads and data validation when online—without locking up the UI. It should support working offline and accessing a data store on the client. It should integrate with hardware devices on the client, such as cameras. Finally, this ideal solution should launch from the Start Menu or an application icon—and exist outside the confines of a Web browser.

Silverlight can deliver on these requirements. Silverlight 3 introduced the concept of an out-of-browser experience, and this has been extended in Silverlight 4. Moreover, Silverlight 4 introduced the capability to interact with specific folders such as “My Pictures,” and with hardware devices such as webcams (applications that use this enhanced functionality will inform the user that the application requires elevated trust and require the user’s consent before the application can be installed—for more information about trusted applications, see this article: msdn.microsoft.com/library/ee721083(v=VS.95)). In this article, we will focus on common issues encountered when architecting an application that supports working both online and offline.

Figure 1 shows a back-of-the-envelope architecture that serves as a good candidate.

image: Candidate High-Level Architecture
Figure 1 Candidate High-Level Architecture

Typical user scenarios for this case include:

  • A mobile worker with a laptop. The laptop may have a 3G card or may connect to a wireless network in an office or Internet hot spot.
  • A user with a desktop PC in an environment with limited connectivity, such as an older or prefabricated office building.

Detecting Network Status

An application that’s expected to operate in an occasionally connected environment must be able to check the current status of the network connection. Silverlight 3 introduced this capability with the NetworkInterface.GetIsNetworkInterfaceAvailable method. Such applications can also make use of NetworkChange.NetworkAddressChangedEvent, which is raised when the IP address of a network interface changes.

So, the first step in equipping an application to deal with a volatile connection is to handle the NetworkChange.NetworkAddressChangedEvent. The obvious place to handle this event is in the App class that acts as an entry point to a Silverlight application. By default, this class will be implemented by App.xaml.cs (for those writing in C#), or App.xaml.vb (for those writing in VB.NET). From here on, we’ll use examples in C#. The Application_StartUp event handler seems like the sensible place to subscribe:

private void Application_Startup(object sender, StartupEventArgs e)
{
  NetworkChange.NetworkAddressChanged += new
    NetworkAddressChangedEventHandler(NetworkChange_ 
    NetworkAddressChanged);
  this.RootVisual = newMainPage();
}

We need the following using statement:

using System.Net.NetworkInformation;

The NetworkChange_NetworkAddressChanged event handler contains the meat of the network detection. Here’s a sample implementation:

void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
{
  this.isConnected = (NetworkInterface.GetIsNetworkAvailable());
  ConnectionStatusChangedHandler handler = 
    this.ConnectionStatusChangedEvent;
  if (handler != null)
  {
    handler(this.isConnected);
  }
}

The first call is to GetIsNetworkAvailable to see if there’s a network connection. In this example, the result is stored in a field that’s exposed via a property, and an event is fired for other parts of the application to use:

private bool isConnected = (NetworkInterface.GetIsNetworkAvailable());

public event ConnectionStatusChangedHandlerConnectionStatusChangedEvent;

public bool IsConnected
{
  get
  { 
    return isConnected;
  }
}

This sample has a skeleton for detecting and handling the current network connectivity. However, even though GetIsNetworkAvailable returns true whenever the computer is connected to a network (other than a loopback or tunnel interface), the network may be connected but not useful. This can be the case when the computer is connected to a router but the router has lost its Internet connection, or when a computer is connected to a public Wi-Fi access point that requires the user to log in via a browser.

Knowing there’s a valid network connection is only part of a robust solution. The Web services a Silverlight application consumes may be unreachable for any number of reasons, and it’s equally important that an occasionally connected application handle this eventuality.

There are a number of approaches for checking that a Web service is available. For first-party Web services—that is, Web services that are under the control of the developer of the Silverlight application—it may be desirable to add a simple no-op method that can be used periodically to determine availability. Where this is not possible, in the case of third-party Web services, for example, or not desirable, the timeout should be appropriately configured and handled. Silverlight 3 uses a subset of the Windows Communication Foundation client configuration—it’s automatically generated when the Add Service Reference tool is used.

Saving Data

Besides being able to react to changes in the network environment, an application also needs to deal with data that is entered when the application is offline. The Microsoft Sync Framework (msdn.microsoft.com/sync) is a comprehensive platform with wide support for data types, data stores, protocols and topologies. At the time of this writing, it’s not available for Silverlight, although it will be. Watch the session from MIX10 at live.visitmix.com/MIX10/Sessions/SVC10 or read the blog post at blogs.msdn.com/sync/archive/2009/12/14/offline-capable-applications-using-silverlight-and-sync-framework.aspx for more information. Clearly, using the Microsoft Sync Framework will be the best option when it becomes available for Silverlight. In the meantime, a simple solution is required to bridge the gap.

An Observable Queue

Ideally, UI elements need not be concerned with whether data is being stored locally or in the cloud—other than where it’s appropriate to signal to the user that the application is currently offline or online. Using a queue is a good way to create this separation between the UI and the data storage code. The component that is processing the queue needs to be able to react to new data being enqueued. All these factors lead to an observable queue. Figure 2 shows a sample implementation of an observable queue.

Figure 2 An Observable Queue

public delegate void ItemAddedEventHandler();

public class ObservableQueue<T>
{
  private readonly Queue<T> queue = new Queue<T>();

  public event ItemAddedEventHandler ItemAddedEvent;

  public void Enqueue(T item)
  {
    this.queue.Enqueue(item);
    ItemAddedEventHandler handler = this.ItemAddedEvent;
    if (handler != null)
    {
      handler();
    }
  }

  public T Peek()
  {
    return this.queue.Peek();
  }

  public T Dequeue()
  {
    return this.queue.Dequeue();
  }

  public ArrayToArray()
  {
    return this.queue.ToArray();
  }

  public int Count
  {
    get
    {
      return this.queue.Count;
    }
  }
}

This simple class wraps a standard queue and raises an event when data is added. It’s a generic class that makes no assumptions about the format or type of data that will be added. An observable queue is useful only when there’s something observing it. In this case, that something is a class called QueueProcessor. Before looking at the code for QueueProcessor, there’s one more consideration: background processing. When the QueueProcessor is notified that new data has been added to the queue, it should process the data on a background thread so the UI stays responsive. To achieve this design goal, the BackgroundWorker class, which is defined in the System.ComponentModel namespace, is ideal.

BackgroundWorker

BackgroundWorker is a convenient way of running operations on a background thread. It exposes two events—ProgressChanged and RunWorkerCompleted—that provide the means to inform an application of a BackgroundWorker task’s progress. The DoWork event is raised when the BackgroundWorker.RunAsync method is called. Here’s a sample that sets up a BackgroundWorker:

private void SetUpBackgroundWorker()
{
  backgroundWorker = new BackgroundWorker();
  backgroundWorker.WorkerSupportsCancellation = true;
  backgroundWorker.WorkerReportsProgress = true;
  backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
  backgroundWorker.ProgressChanged += new
    ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
  backgroundWorker.RunWorkerCompleted += new
    RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
}

Note that the code in the event handler for the DoWork event should periodically check to see if a cancellation is pending. See the MSDN documentation at msdn.microsoft.com/library/system.componentmodel.backgroundworker.dowork%28VS.95%29 for more detail.

IWorker

In keeping with the generic nature of the ObservableQueue, it would be good to separate the definition of the work to be done from the creation and configuration of the BackgroundWorker. A simple interface—IWorker—defines the event handler for DoWork. The sender object in the event handler signature will be the BackgroundWorker, which enables classes implementing the IWorker interface to report progress and check for pending cancellation. Here’s the definition of IWorker:

public interface IWorker
{
  void Execute(object sender, DoWorkEventArgs e);
}

It’s easy to indulge in design and make separations where none are needed. The idea for creating the IWorker interface came from practical experience. The ObservableQueue as presented in this article was certainly intended to be part of a solution for an occasionally connected application. However, it turned out that other tasks, such as importing photographs from a digital camera, were also more easily implemented with an ObservableQueue. For example, when paths to photographs are placed on an ObservableQueue, an implementation of IWorker can process the images in the background. Making the ObservableQueue generic and creating the IWorker interface enabled such scenarios while still addressing the original issue.

Processing the Queue

QueueProcessor is the class that ties the ObservableQueue and the IWorker implementation together to do something useful. Processing the queue is a matter of setting up a BackgroundWorker, which includes setting the Execute method of the IWorker as the event handler for the BackgroundWorker.DoWork event and subscribing to the ItemAddedEvent. Figure 3 shows a sample implementation of QueueProcessor.

Figure 3 Implementing a QueueProcessor

public class QueueProcessor<T>
{
  private BackgroundWorker backgroundWorker;
  private readonly IWorker worker;

  public QueueProcessor(ObservableQueue<T>queueToMonitor, IWorker worker)
  {
    ((SampleCode.App)Application.Current).ConnectionStatusChangedEvent += new
       ConnectionStatusChangedEventHandler(QueueProcessor_
         ConnectionStatusChangedEvent);
    queueToMonitor.ItemAddedEvent += new
      ItemAddedEventHandler(PendingData_ItemAddedEvent);
    this.worker = worker;
    SetUpBackgroundWorker();
    if ((((SampleCode.App)Application.Current).IsConnected) && 
      (!backgroundWorker.IsBusy) 
      && (((SampleCode.App)Application.Current).PendingData.Count>0))
    {
      backgroundWorker.RunWorkerAsync();
    }
  }

  private void PendingData_ItemAddedEvent()
  {
    if ((((SampleCode.App)Application.Current).IsConnected) && 
      (!backgroundWorker.IsBusy))
    {
      backgroundWorker.RunWorkerAsync();
    }
  }

  private void SetUpBackgroundWorker()
  {
    backgroundWorker = new BackgroundWorker();
    backgroundWorker.WorkerSupportsCancellation = true;
    backgroundWorker.WorkerReportsProgress = true;
    backgroundWorker.DoWork += new DoWorkEventHandler(this.worker.Execute);
    backgroundWorker.ProgressChanged += new
      ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
    backgroundWorker.RunWorkerCompleted += new
      RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
  }

  private void backgroundWorker_RunWorkerCompleted(object sender,
    RunWorkerCompletedEventArgs e)
  {
    if (e.Cancelled)
    {
      // Handle cancellation
    }
      else if (e.Error != null)
      {
        // Handle error
      }
    else
    {
      // Handle completion if necessary
    }
  }

  private void backgroundWorker_ProgressChanged(object sender, 

    ProgressChangedEventArgs e)
  {
     // Raise event to notify observers
  }

  private void QueueProcessor_ConnectionStatusChangedEvent(bool isConnected)
  {
    if (isConnected)
    {
      if (!backgroundWorker.IsBusy)
      {
        backgroundWorker.RunWorkerAsync();
      }
    }
    else
    {
      backgroundWorker.CancelAsync();
    }
  }
}

The sample code in Figure 3 leaves implementing some of BackgroundWorker’s capabilities, such as handling errors, as exercises for the reader. The RunAsync method is invoked only if the application is currently connected and the BackgroundWorker is not already busy processing the queue.

UploadWorker

The constructor of QueueProcessor requires an IWorker, which means, of course, that a concrete implementation is needed for uploading data to the cloud. UploadWorker seems like a sensible name for such a class. Figure 4 shows a sample implementation.

Figure 4 Uploading Data to the Cloud

public class UploadWorker :  IWorker
{
  public override void Execute(object sender, DoWorkEventArgs e)
  {
    ObservableQueue<DataItem>pendingData = 
      ((SampleCode.App)Application.Current).PendingData;
    while (pendingData.Count>0)
    {
      DataItem item = pendingData.Peek();
      if (SaveItem(item))
      {
        pendingData.Dequeue();
      }
    }
  }

  private bool SaveItem(DataItem item)
  {
    bool result = true;
    // Upload item to webservice
    return result;
  }
}

In Figure 4, the Execute method uploads queued items. If they can’t be uploaded, they remain in the queue. Note that if access to the BackgroundWorker is required—for example, to report progress or check for pending cancellation—the sender object is the BackgroundWorker. If a result is assigned to the Result property of e, the DoWorkEventArgs, it will be available in the RunWorkerCompleted event handler.

Isolated Storage

Putting data into a queue and submitting only that data to a Web service while connected (so it can be stored in the cloud) is fine, provided the application never closes. In the event that the application is closed while pending data remains in the queue, a strategy is needed to store that data until the application is next loaded. Silverlight provides Isolated Storage for this situation.

Isolated Storage is a virtual file system available to Silverlight applications that enables local storage of data. It can store a limited amount of data (the default limit is 1MB), but an application can request more space from the user. In our example, serializing the queue to Isolated Storage will save the queue state between application sessions. A simple class called QueueStore will do the trick, as shown in Figure 5.

Figure 5 Serializing a Queue to Isolated Storage

public class QueueStore
{
  private const string KEY = "PendingQueue";
  private IsolatedStorageSettings appSettings = 
    IsolatedStorageSettings.ApplicationSettings;

  public void SaveQueue(ObservableQueue<DataItem> queue)
  {
    appSettings.Remove(KEY);
    appSettings.Add(KEY, queue.ToArray());
    appSettings.Save();
  }

  public ObservableQueue<DataItem>LoadQueue()
  {
    ObservableQueue<DataItem> result = new ObservableQueue<DataItem>();
    ArraysavedArray = null;

    if (appSettings.TryGetValue<Array>(KEY, out savedArray))
    {
      foreach (var item in savedArray)
      {
        result.Enqueue(item as DataItem);
      }
    }

  return result;
  }
}

Provided that items on the queue are serializable, QueueStore will enable saving and loading queues. Calling the Save method in the Application_Exit method of App.xaml.cs and the Load method in the Application_Startup method of App.xaml.cs enables the application to save state between sessions.

The items added to the queue have been of type DataItem. This is a simple class that represents the data. In the case of a slightly richer data model, DataItem might hold a simple object graph. In more complex scenarios, DataItem might be a base class from which other classes inherit.

Retrieving Data

For an application to be usable when it’s only sometimes connected, it must have some way of caching data locally. The first consideration for this is the size of the working set of data an application requires. In a simple application, it may be possible for a local cache to contain all the data the application requires. For example, a simple reader application that consumes RSS feeds may need to cache only the feeds and the user preferences. For other applications, the working set may be too large or it may simply be too difficult to predict which data the user requires, which effectively increases the size of the working set of data.

A simple application is an easier place to start. Data that’s required throughout the application session, such as user preferences, can be downloaded at application startup and held in memory. Should this data be changed by the user, the upload strategies previously discussed can be applied. But this approach assumes the application will be connected at startup, which may not be the case. Isolated Storage is once again the answer, and the samples presented earlier will cater to this scenario with the addition of a call to download the server version of the data where appropriate. Bear in mind that the user may have the application installed on both a work PC and a home PC, so the appropriate time could be when a user returns to the application after a significant pause.

Another scenario might involve a simple application that is displaying relatively static data, such as news. A similar strategy can be applied: download the data when possible, hold it in memory and persist to Isolated Storage at application shutdown (and reload from Isolated Storage at startup). When a connection is available, the cached data can be invalidated and refreshed. When the connection is available for more than a few minutes, the application should periodically refresh data such as news. As discussed earlier, the UI should be unaware of this background work.

In the case of downloading news, the starting point is a simple NewsItem class:

public class NewsItem
{
  public string Headline;
  public string Body;

  public override stringToString()
  {
    return Headline;
  }
}

This class is oversimplified for the sake of the example, and ToString is overridden to make it easy to bind to in the UI. To store the downloaded news, a simple Repository class, which downloads news in the background, is required, as shown in Figure 6.

Figure 6 Storing Downloaded News in a Repository Class

public class Repository
{
  private ObservableCollection<NewsItem> news = 
    new ObservableCollection<NewsItem>();
  private DispatcherTimer timer = new DispatcherTimer();
  private const int TIMER_INTERVAL = 1;

public Repository()
{
  ((SampleCode.App)Application.Current).ConnectionStatusChangedEvent += 
    new ConnectionStatusChangedHandler(Repository_
    ConnectionStatusChangedEvent);
  if (((SampleCode.App)Application.Current).IsConnected)
  {
    RetrieveNews();
    StartTimer();
  }
}

private void Repository_ConnectionStatusChangedEvent(bool isConnected)
{
  if (isConnected)
  {
    StartTimer();
  }
  else
  {
    StopTimer();
  }
}

private void StopTimer()
{
  this.timer.Stop();
}

private void StartTimer()
{
  this.timer.Interval = TimeSpan.FromMinutes(1);
  this.timer.Tick += new EventHandler(timer_Tick);
  this.timer.Start();
}

voidtimer_Tick(object sender, EventArgs e)
{
  if (((SampleCode.App)Application.Current).IsConnected)
  {
    RetrieveNews();
  }
}

private void RetrieveNews()
{
  // Get latest news from server
  List<NewsItem> list = GetNewsFromServer();
  if (list.Count>0)
  {
    lock (this.news)
    {
      foreach (NewsItem item in list)
      {
        this.news.Add(item);
      }
    }
  }
}

private List<NewsItem>GetNewsFromServer()
{
  // Simulate retrieval from server
  List<NewsItem> list = new List<NewsItem>();
  for (int i = 0; i <5; i++)
  {
    NewsItemnewsItem = new NewsItem()
    { Headline = "Something happened at " + 
        DateTime.Now.ToLongTimeString(),
        Body = "On " + DateTime.Now.ToLongDateString() + 
        " something happened.  We'll know more later." };
      list.Add(newsItem);
    }
    return list;
  }

  public ObservableCollection<NewsItem> News
  {
    get
    {
      return this.news;
    }
    set
    {
      this.news = value;
    }
  }
}

In Figure 6, the retrieval of news is simulated for brevity. The Repository class subscribes to the ConnectionStatusChangedEvent and, when connected, uses a DispatcherTimer to retrieve news at specified intervals. A DispatcherTimer is used in conjunction with an ObservableCollection to enable simple data binding. The DispatcherTimer is integrated into the Dispatcher queue, so it runs on the UI thread. The effect of updating an ObservableCollection is that an event is raised such that a bound control in the UI will automatically update, which is ideal in the case of downloading news. A System.Threading.Timer is available in Silverlight, but it doesn’t run on the UI thread. In this case, any operation that accesses objects on the UI thread needs to be called using Dispatcher.BeginInvoke.

To use the Repository requires only a property in App.xaml.cs. Given the Repository subscribes to the ConnectionStatusChangedEvent in its constructor, the best place to instantiate it is in Application_StartupeventinApp.xaml.cs.

It’s unlikely that data such as user preferences will be changed by anyone other than the user while the application is offline, although in the case of applications that are used across a range of devices by the same user, it’s certainly possible. Data such as news stories also tends not to change. This means the cached data is likely to be valid and there are unlikely to be synchronization issues upon reconnection. However, in the case of volatile data, a different approach may be required. For example, it might make sense to inform the user when the data was last retrieved so he can respond appropriately. If the application needs to make decisions based on volatile data, rules for its expiration are required, along with notification to the user that the required data is not available due to the application being offline.

If data can be changed, comparing when and where it was changed will enable conflict resolution in many cases. For example, an optimistic approach would assume that the most recent version is the most valid and therefore wins any conflict, but you could also decide to resolve the conflict based on where the data was updated. Knowledge of the application topology and usage scenarios is the key to the right approach. Where conflicting versions can’t—or shouldn’t—be resolved, a log of such versions should be stored and the appropriate users notified so they can make a judgement.

You’ll also need to consider where to locate the synchronization logic. The simplest conclusion is that it should reside on the server so it can mediate between conflicting versions—in this case, the UploadWorker would require a minor modification such that it updated DataItem to the latest server version. As noted earlier, the Microsoft Sync Framework will eventually take care of many of these issues, leaving the developer free to concentrate on the application domain.

Assembling the Parts

With all the parts discussed, a simple Silverlight application that explores these capabilities in an occasionally connected environment is easy to create. Figure 7 shows a screenshot of such an application.

image: Sample Application for Demonstrating Network Status and Queues
Figure 7 Sample Application for Demonstrating Network Status and Queues

In the example in Figure 7, the Silverlight application is running out-of-browser. Given the nature of an occasionally connected application, it’s likely that many will run out-of-browser, because they’re easily accessible to the user from the start menu and desktop and can run regardless of the network connection. A Silverlight application that runs in the context of a browser, in contrast, requires a network connection so the Web server on which it resides can serve up the Web page and Silverlight application.

This simple UI in Figure 7, while almost certainly not in line to win a design award, provides a means to exercise the sample code. In addition to displaying the current network status, it has fields to enter data and a list box bound to the News property of the Repository. The sample XAML to create the screen is shown in Figure 8. The code-behind is shown in Figure 9.

Figure 8 XAML for Sample Application UI

<UserControl x:Class="SampleCode.MainPage" 
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
  xmlns:dataInput="clr-namespace:System.Windows.Controls;
    assembly=System.Windows.Controls.Data.Input" 
    Width="400" Height="300">
      <Grid x:Name="LayoutRoot" Background="White" ShowGridLines="False">
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="12" />
          <ColumnDefinition Width="120" />
          <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="12" />
          <RowDefinition Height="64" />
          <RowDefinition Height="44" />
          <RowDefinition Height="34" />
          <RowDefinition Height="34" />
          <RowDefinition Height="100" />
        </Grid.RowDefinitions>
        <Ellipse Grid.Row="1" Grid.Column="1" Height="30" HorizontalAlignment="Left" 
          Name="StatusEllipse" Stroke="Black" StrokeThickness="1" 
          VerticalAlignment="Top" Width="35" />
        <Button Grid.Row="4" Grid.Column="1" Content="Send Data" Height="23" 
          HorizontalAlignment="Left" Name="button1" VerticalAlignment="Top" 
          Width="75" Click="button1_Click" />
        <TextBox Grid.Row="2" Grid.Column="2" Height="23" HorizontalAlignment="Left" 
          Name="VehicleTextBox" VerticalAlignment="Top" Width="210" />
        <TextBox Grid.Row="3" Grid.Column="2" Height="23" HorizontalAlignment="Left" 
          Name="TextTextBox" VerticalAlignment="Top" Width="210" />
        <dataInput:Label Grid.Row="2" Grid.Column="1" Height="28" 
          HorizontalAlignment="Left" Name="VehicleLabel" VerticalAlignment="Top" 
          Width="120" Content="Title" />
        <dataInput:Label Grid.Row="3" Grid.Column="1" Height="28" 
          HorizontalAlignment="Left" Name="TextLabel" VerticalAlignment="Top" 
          Width="120" Content="Detail" />
        <ListBox Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="2" Height="100" 
          HorizontalAlignment="Left" Name="NewsListBox" 
          VerticalAlignment="Top" Width="376" />
      </Grid>
</UserControl>

Figure 9 Code-Behind for Sample Application UI

public delegate void DataSavedHandler(DataItem data);

public partial class MainPage : UserControl
{
  private SolidColorBrush STATUS_GREEN = new SolidColorBrush(Colors.Green);
  private SolidColorBrush STATUS_RED = new SolidColorBrush(Colors.Red);

  public event DataSavedHandlerDataSavedEvent;

  public MainPage()
  {
    InitializeComponent();
    ((SampleCode.App)Application.Current).ConnectionStatusChangedEvent += new
      ConnectionStatusChangedHandler(MainPage_ConnectionStatusChangedEvent);
    IndicateStatus(((NetworkStatus.App)Application.Current).IsConnected);
    BindNews();
  }

  private void MainPage_ConnectionStatusChangedEvent(bool isConnected)
  {
    IndicateStatus(isConnected);
  }

  private void IndicateStatus(bool isConnected)
  {
    if (isConnected)
    {
      StatusEllipse.Fill = STATUS_GREEN;
    }
    else
    {
      StatusEllipse.Fill = STATUS_RED;
    }
  }

  private void BindNews()
  {
    NewsListBox.ItemsSource = 
      ((SampleCode.App)Application.Current).Repository.News;
  }

  private void button1_Click(object sender, RoutedEventArgs e)
  {
    DataItem dataItem = new DataItem
    {
      Title = this.TitleTextBox.Text,
      Detail = this.DetailTextBox.Text
    };
    DataSavedHandler handler = this.DataSavedEvent;
    if (handler != null)
    {
      handler(dataItem);
    }

    this.TitleTextBox.Text = string.Empty;
    this.DetailTextBox.Text = string.Empty;
  }
}

The class in Figure 9 raises an event to indicate data has been saved. An observer (in this case App.xaml.cs) subscribes to this event and puts the data on the ObservableQueue.

A New Class of Application

Silverlight support for applications that are connected only some of the time enables a new class of application. Such applications introduce new considerations that require developers to think about how the application should behave when connected and when disconnected. This article presents these considerations and provides strategies and sample code for addressing them. Of course, given the breadth of scenarios that exist in a sometimes-connected world, the samples can serve only as a starting point.


Mark Bloodworth is an architect in the Developer and Platform Evangelism Team at Microsoft, where he works with companies on innovative projects. Prior to joining Microsoft, he was the chief solutions architect at BBC Worldwide, where he led a team responsible for architecture and systems analysis. Most of his career has been focused on using Microsoft technologies, especially the Microsoft .NET Framework, with a little Java thrown in for good measure. He keeps a blog at remark.wordpress.com.

Dave Brown has worked for Microsoft for more than nine years, initially for Microsoft Consulting Services in Internet-related technologies. He currently works in the Developer and Platform Evangelism Team in the United Kingdom as an architect for the Microsoft Technology Centre. In this role, he splits his time between business analysis of customer scenarios, design of solution architecture, and both managing and developing code for proof-of-concept solutions. His blog can be found at drdave.co.uk/blog.

Thanks to the following technical expert: Ashish Shetty