XAML Playground
about XAML and other Amenities

Metro: A simple class to handle retry on network calls

2012-05-18T00:31:23+01:00 by codeblock

After you start working with metro-style applications, you may be concerned by the extensively adopted asyncronous model. If you ever used Silverlight you can be aware that this model comes directly as an inheritance from it. In metro-style applications this model is taken to the extreme consequences. As an example WinRT exposes all the operations that takes longer than 50ms with an asyncronous pattern. But the most frequent case where you meet asynchronicity, is when you call the network.

The use of Task Parallel Library offers anumber of interesting opportunities to handle asynchronous operations and the benefits can make your code most simple and reusable. Recently I've created an interesting class (I called it NetworkCallManager). This class wraps every network call and let you control the flow of a call in the case it returns an error. It is common to handle timeouts, authentication issues and poor network connectivity and if you read the guidelines you know that they suggests you to let the user retry the failed operation. Here is the code I wrote:

   1: public class NetworkCallManager
   2: {
   3:     public event EventHandler<NetworkErrorEventArgs> NetworkError;
   4:  
   5:     protected virtual void OnNetworkError(NetworkErrorEventArgs args)
   6:     {
   7:         EventHandler<NetworkErrorEventArgs> handler = this.NetworkError;
   8:  
   9:         if (handler != null)
  10:             handler(this, args);
  11:     }
  12:  
  13:     public async Task<T> Execute<T>(Func<Task<T>> task)
  14:     {
  15:         bool retry = false;
  16:  
  17:         do
  18:         {
  19:             Exception error = null;
  20:  
  21:             try
  22:             {
  23:                 return await task();
  24:             }
  25:             catch (Exception ex)
  26:             {
  27:                 error = ex;
  28:             }
  29:  
  30:             if (error != null)
  31:             {
  32:                 NetworkErrorEventArgs args = new NetworkErrorEventArgs(error);
  33:                 this.OnNetworkError(args);
  34:                 retry = !args.Cancel;
  35:             }
  36:  
  37:         } while (retry);
  38:  
  39:         throw new NetworkOperationCancelledException("Network operation has been cancelled");
  40:     }
  41: }

The main method, responsible to place the network call, is named Execute. It receives a Task<T> where T is compatible with the return type of the network call we have to execute. The important thing to understand is that you have tu use a lambda expression because the code inside the method can recall this function lot of times. The method works as a pass-through function since it returns the same type as the function passed. So when the task is completed you can get directly the result.

Inside the body the method starts a loop where it runs the task and handles exceptions. When an exception is raised it gracefully handle the error and notifies it to the caller. The user can choose to break the operation or to retry. Here is how you can use the class:

   1: // omissis
   2:  
   3: NetworkCallManager ncm = new NetworkCallManager();
   4: ncm.NetworkError += HandleNetworkError;
   5:  
   6: try
   7: {
   8:     this.Speeches = await ncm.Execute<IEnumerable<SpeechDTO>>(() => this.DataService.GetSpeechesInYears(5));
   9: }
  10: catch (NetworkOperationCancelledException ex)
  11: {
  12:     // swallow
  13: }
  14: catch (Exception ex)
  15: {
  16:     this.DialogService.ShowGenericError(ex.Message);
  17: }
  18: finally
  19: {
  20:     ncm.NetworkError -= HandleNetworkError;
  21: }
  22:  
  23: private async void HandleNetworkError(object sender, NetworkErrorEventArgs e)
  24: {
  25:     e.Cancel = await this.DialogService.ShowNetworkError(e.Error);
  26: }
  27:  
  28: // omissis

This pattern let you to use the wrapper in a trasparent way and directy get the result. It also raises a specific exception when the user choose to stop the operation instead of retry. To me this spered me to implemend this flow directly into the page.