Delen via


Accessing Data in the Cloud

patterns & practices Developer Center

On this page: Download:
You Will Learn | Goals and Requirements | Overview of the Solution - Exposing the Data in the Cloud, Data Formats, Consuming the Data, Using SSL | Inside the Implementation - Creating a WCF REST Service in the Cloud, Consuming the Data in the Windows Phone Client Application Download code samples
Download book as PDF

You Will Learn

  • How to expose data in the cloud for consumption.
  • How the mobile client application reliably retrieves data from the cloud.
  • How the mobile client application reliably sends data to the cloud.

The Surveys application stores its data in the cloud using a combination of Windows Azure tables and blobs. The mobile client application needs to access this data. For example, it must be able to retrieve survey definitions before it can display the questions to the phone user, and it must be able to save completed survey responses back to the cloud where they will be available for analysis by the survey creator.

Goals and Requirements

The mobile client application must be able to reliably download survey definitions and reliably upload survey responses. Making sure that surveys download reliably is important because survey creators want to be sure that surveys are delivered to all potential surveyors in order to maximize the number of responses. Making sure that surveys upload reliably is important because survey creators want to receive the maximum number of completed surveys, with no duplication of results.

The developers at Tailspin are aware that some surveyors may have limited bandwidth available, so they wanted to control the amount of bandwidth used to transfer data between the phone and the service. In addition to this, the developers at Tailspin want to make sure that the application performs well when it transfers data to and from the cloud.

The developers also wanted a solution that was as simple as possible to implement, and that they could easily customize in the future if, for example, authentication requirements were to change.

Finally, again with a view to the future, the developers wanted a solution that could potentially work with platforms other than Windows Phone.

Overview of the Solution

Tailspin considered three separate aspects of the solution: how to implement the server, how to implement the client, and the format of the data that the application moves between the phone and the cloud.

Exposing the Data in the Cloud

The developers at Tailspin decided to use the Windows Communication Foundation (WCF) Representational State Transfer (REST) programming model to expose the data in the Tailspin Surveys service.

Tailspin is using the WCF REST programming model to exchange data between the mobile client and the cloud-based service.

Hh821034.note(en-us,PandP.10).gifJana Says:
Jana
                In the future, Tailspin would like to use WCF Data Services because of its support for the OData standard. For more information about OData, see the <a href="https://www.odata.org/">Open Data Protocol</a> website.</td>

Data Formats

Because Tailspin is using a custom WCF service, they must use custom data transfer objects to exchange data between the mobile client application and the cloud-based service. Tailspin chose to use a JavaScript Object Notation (JSON) format for moving the data over the wire because it produces a compact payload that reduces bandwidth requirements, is relatively easy to use, and will be usable on platforms other than the Windows Phone platform.

Note

If, in the future, Tailspin moves to WCF Data Services, it will no longer require the custom data transfer objects because WCF Data Services use the OData protocol to move data over the wire.

Tailspin also considered compressing the data before transferring it over the network to reduce bandwidth utilization, but the developers at Tailspin decided that the additional CPU and battery usage on the phone that this would require outweighed the benefits in this particular case. You should evaluate this tradeoff between the cost of bandwidth and battery consumption in your own application before you decide whether to compress data you need to move over the network.

Hh821034.note(en-us,PandP.10).gifChristine Says:
Christine
                On the phone, additional CPU usage affects both the responsiveness of the device and its battery life.</td>

Consuming the Data

The developers at Tailspin implemented a set of custom classes in the mobile client application to handle the data transfer with the Tailspin web service. The classes on the mobile client interact with the WCF REST service and parse the data received from the service. Tailspin's analysis of the data transfer requirements for the Windows Phone application identified only two types of interaction with the service: a "Get Surveys" operation and a "Send Survey Result" operation, so the implementation of a custom client should be quite simple. Furthermore, the "Send Survey Result" operation always appends the result to the store on the server so there are no concurrency issues, and survey creators cannot modify a survey design after they publish it so there are no versioning issues.

Note

In the future, Tailspin may decide to use WCF Data Services and the OData protocol. OData client libraries, including a version for Windows Phone, are available for download on the Open Data Protocol website.
Using the OData Client Library would minimize the amount of code that the developers would have to write because the library and the code generated by using the DataSvcUtil utility fully encapsulate the calls to the WCF Data Service endpoint. Furthermore, using the client library offers advanced features such as batching, client-side state management, and conflict resolution.
For a walkthrough that shows how to use the OData Client Library on the Windows Phone platform, see the post, "Walkthrough: Consuming OData with MVVM for Windows Phone," on MSDN.

Tailspin also evaluated the Microsoft Sync Framework to handle the synchronization of data between the phone and Windows Azure, but it was decided that the simplicity of the synchronization required by the Tailspin mobile client application did not warrant this.

Using SSL

Self-signed certificates are not supported on the Windows Phone device, so to implement SSL, it is necessary to use a server certificate from a trusted third-party company, such as VeriSign. Therefore, the sample application does not secure the WCF REST service with SSL, so a malicious client can impersonate the phone client and send malicious data.

Hh821034.note(en-us,PandP.10).gifPoe Says:
Poe
                You should protect any sensitive data that you need to transfer over the network between the Windows Phone client and the Windows Azure-hosted services by using SSL.</td>

Inside the Implementation

Now is a good time to take a more detailed look at the code that enables the mobile client application to access data in the cloud. As you go through this section, you may want to download the Windows Phone Tailspin Surveys application from the Microsoft Download Center.

Creating a WCF REST Service in the Cloud

The TailSpin.Services.Surveys project includes a standard WCF REST Service named SurveysService hosted in Windows Azure that exposes the survey data to the Windows Phone client application. The Windows Azure web role defined in the TailSpin.Services.Surveys.Host.Azure project populates a routing table that includes a route to the Surveys service in its Global.asax.cs file. The following code example shows how the RegisterRoutes method creates the RouteTable object.

public class Global : HttpApplication
{
  protected void Application_Start(object sender, EventArgs e)
  {
    ...
    RegisterRoutes();
  }

  private static void RegisterRoutes()
  {
    var customServiceHostFactory = new 
      CustomServiceHostFactory(ContainerLocator.Container);
    RouteTable.Routes.Add(new ServiceRoute("Registration",
      customServiceHostFactory, typeof(RegistrationService)));
    RouteTable.Routes.Add(new ServiceRoute("Survey", 
      customServiceHostFactory, typeof(SurveysService)));
  }
}

This SurveysService class implements the WCF REST service endpoints. The following code example shows the GetSurveys method in the SurveysService class that exposes the surveys data stored in Windows Azure storage.

Hh821034.note(en-us,PandP.10).gifPoe Says:
Poe You should secure all your Windows Azure BLOB, table, and queue endpoints using SSL.
public SurveyDto[] GetSurveys(string lastSyncUtcDate)
{
  DateTime fromDate;
  if (!string.IsNullOrEmpty(lastSyncUtcDate))
  {
    if (DateTime.TryParse(lastSyncUtcDate, out fromDate))
    {
      fromDate = DateTime.SpecifyKind(fromDate, DateTimeKind.Utc);
    }
    else
    {
      throw new FormatException("lastSyncUtcDate is in an incorrect format. 
        The format should be: yyyy-MM-ddTHH:mm:ss");
    }
  }
  else
  {
    fromDate = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc);
  }

  var username = Thread.CurrentPrincipal.Identity.Name;

  return this.filteringService
    .GetSurveysForUser(username, fromDate)
    .Select(s => new SurveyDto
    {
      SlugName = s.SlugName,
      Title = s.Title,
      Tenant = s.Tenant,
      Length = 5 * s.Questions.Count,
      IconUrl = this.GetIconUrlForTenant(s.Tenant),
      CreatedOn = s.CreatedOn,
      Questions = s.Questions.Select(q => new QuestionDto
      {
        PossibleAnswers = q.PossibleAnswers,
        Text = q.Text,
        Type = Enum.GetName(typeof(QuestionType), q.Type)
      }).ToList()
    }).ToArray();
}

This example shows how the Surveys service returns survey definitions to the mobile client in an array of SurveyDto objects that represent surveys added to the service after a specified date. It also demonstrates how you can apply a filter to the request. In the current version of the application, the filter returns a list of surveys from the phone user's preferred list of tenants. For more information about how Tailspin implemented the filtering behavior, see the section, "Filtering Data," later in this chapter.

To enable the mobile client to upload survey answers, the SurveysService class provides two methods: one for uploading individual images and sound clips, and one for uploading complete survey answers. The following code example shows how the AddMediaAnswer method saves an image or a sound clip to Windows Azure blob storage and returns a URI that points to the blob.

public string AddMediaAnswer(Stream media, string type)
{
  var questionType = (QuestionType)Enum.Parse(typeof(QuestionType), type);
  return this.mediaAnswerStore.SaveMediaAnswer(media, questionType);
}

The following code example shows the AddSurveyAnswers method that receives an array of SurveyAnswerDto objects that it unpacks and saves in the survey answer store in Windows Azure storage.

public void AddSurveyAnswers(SurveyAnswerDto[] surveyAnswers)
{
  foreach (var surveyAnswerDto in surveyAnswers)
  {
    this.surveyAnswerStore.SaveSurveyAnswer(new SurveyAnswer
    {
      Title = surveyAnswerDto.Title,
      SlugName = surveyAnswerDto.SlugName,
      Tenant = surveyAnswerDto.Tenant,
      StartLocation = surveyAnswerDto.StartLocation,
      CompleteLocation = surveyAnswerDto.CompleteLocation,
      QuestionAnswers = surveyAnswerDto.QuestionAnswers
      .Select(qa => new QuestionAnswer
      {
        QuestionText = qa.QuestionText,
        PossibleAnswers = qa.PossibleAnswers,
        QuestionType = (QuestionType)Enum.Parse(
          typeof(QuestionType), qa.QuestionType),
        Answer = qa.Answer
      }).ToList()
    });
  }
}

Consuming the Data in the Windows Phone Client Application

The mobile client application uses the methods exposed by the Surveys service to send and receive survey data.

The following code example shows the ISurveysServiceClient interface that defines the set of asynchronous WCF REST calls that the mobile client application can make.

public interface ISurveysServiceClient
{
  IObservable<IEnumerable<SurveyTemplate>> GetNewSurveys(string lastSyncDate);
  IObservable<Unit> SaveSurveyAnswers(IEnumerable<SurveyAnswer> surveyAnswers);
}

The SurveysServiceClient class implements this interface, and the following code example shows the GetNewSurveys method that sends the request to the service and returns the observable list of surveys to the application. This method makes the asynchronous web request by using the GetJson method from the HttpClient class, converts the returned data transfer objects to SurveyTemplate objects, and then returns an observable sequence of SurveyTemplate objects.

public IObservable<IEnumerable<SurveyTemplate>> 
  GetNewSurveys(string lastSyncDate)
{
  var surveysPath = string.Format(CultureInfo.InvariantCulture, 
    "Surveys?lastSyncUtcDate={0}", lastSyncDate);
  var uri = new Uri(this.serviceUri, surveysPath);

  return
    httpClient
      .GetJson<IEnumerable<SurveyDto>>(new HttpWebRequestAdapter(uri), 
       settingsStore.UserName, settingsStore.Password)
      .Select(ToSurveyTemplate);
}

Note

For more information about the HttpClient class that makes the asynchronous web request, see the section, "Registering for Notifications," earlier in this chapter.

To save completed survey answers to the Surveys service, the client must first save any image and sound-clip answers. It uploads each media answer in a separate request, and then it includes the URL that points to the blob that contains the media when the client uploads the complete set of answers for a survey. It would be possible to upload the survey answers and the media in a single request, but this may require a large request that exceeds the maximum upload size configured in the service. By uploading all the media items first, the worst that can happen, if there is a failure, is that there are orphaned media items in Windows Azure storage.

Hh821034.note(en-us,PandP.10).gifMarkus Says:
Markus Tailspin decided to support clients that upload individual media items instead of using multi-part messages in order to support the widest set of possible client platforms.

The following code example shows the first part of the SaveAndUpdateMediaAnswers method that creates a list of answers that contain media.

var mediaAnswers =
  from surveyAnswer in surveyAnswersDto
  from answer in surveyAnswer.QuestionAnswers
  where answer.Answer != null &&
    (answer.QuestionType ==
    Enum.GetName(typeof(QuestionType), QuestionType.Picture) ||
    answer.QuestionType ==
    Enum.GetName(typeof(QuestionType), QuestionType.Voice))
  select answer;

The method then iterates over this list and asynchronously creates HTTP requests to post each media item to the Tailspin Surveys service.

foreach (var answer in mediaAnswers)
{
  var mediaAnswerPath = string.Format(CultureInfo.InvariantCulture,
    "MediaAnswer?type={0}", answer.QuestionType);
  var mediaAnswerUri = new Uri(this.serviceUri, mediaAnswerPath);
  byte[] mediaFile = GetFile(answer.Answer);

  var request = httpClient.GetRequest(
    new HttpWebRequestAdapter(mediaAnswerUri),
    settingsStore.UserName, settingsStore.Password);
  request.Method = "POST";
  request.Accept = "application/json";
  ...
}

The method then asynchronously sends each of these requests and retrieves the response to each request in order to extract the URL that points to the blob where the service saved the media item. It must then assign the returned URLs to the Answer property of the correct QuestionAnswerDto data transfer object. The following code example shows how the SaveAndUpdateMediaAnswers method sends the media answers to the Tailspin Surveys service asynchronously. The code example shows how this operation is broken down into the following steps:

  1. The method first makes an asynchronous call to access the HTTP request stream as an observable object.
  2. The method writes the media item to the request stream and then makes an asynchronous call to access the HTTP response stream.
  3. It reads the URL of the saved media item from the response stream and sets the Answer property of the QuestionAnswerDto object.
  4. The ForkJoin method makes sure that the media answers are uploaded in parallel, and after all the media answers are uploaded, the method returns an IObservable<Unit> object.
var mediaAnswerObservables = new List<IObservable<Unit>>();
foreach (var answer in mediaAnswers)
{
  ...

  QuestionAnswerDto answerCopy = answer;
  var saveFileAndUpdateAnswerObservable = Observable
    .FromAsyncPattern<Stream>(request.BeginGetRequestStream, 
    request.EndGetRequestStream)()
    .SelectMany(requestStream =>
    {
      using (requestStream)
      {
        requestStream.Write(mediaFile, 0, mediaFile.Length);
        requestStream.Close();
      }
      return Observable.FromAsyncPattern<WebResponse>(
        request.BeginGetResponse, request.EndGetResponse)();
    },
    (requestStream, webResponse) =>
    {
      using (var responseStream = webResponse.GetResponseStream())
      {
        var responseSerializer = new
          DataContractJsonSerializer(typeof(string));
        answerCopy.Answer = 
         (string)responseSerializer.ReadObject(responseStream);
      }

      return new Unit();
    });
     
  mediaAnswerObservables.Add(saveFileAndUpdateAnswerObservable);
}

return mediaAnswerObservables.ForkJoin().Select(u => new Unit());

The following code example shows how the mobile client uploads completed survey answers. It first creates the data transfer objects, then it uploads any media answers using the SaveAndUpdateMediaAnswers method described earlier, and finally, it uploads the SurveyAnswerDto object using the HttpClient class. The SelectMany method here makes sure that all the media answers are uploaded before the SurveyAnswerDto object.

public IObservable<Unit> 
  SaveSurveyAnswers(IEnumerable<SurveyAnswer> surveyAnswers)
{
  var surveyAnswersDto = ToSurveyAnswersDto(surveyAnswers);
  
  var saveAndUpdateMediaAnswersObservable = 
    SaveAndUpdateMediaAnswers(surveyAnswersDto);

  var uri = new Uri(this.serviceUri, "SurveyAnswers");
  var saveSurveyAnswerObservable =
    httpClient.PostJson(new HttpWebRequestAdapter(uri),
    settingsStore.UserName, settingsStore.Password,
    surveyAnswersDto);

  return saveAndUpdateMediaAnswersObservable.SelectMany(
    u => saveSurveyAnswerObservable);
}

Both the GetNewSurveys and SaveSurveyAnswer methods are called from the SurveysSynchronizationService class that is responsible for coordinating which surveys should be downloaded and which survey answers should be uploaded. For a description of the synchronization process, see Chapter 3, "Using Services on the Phone."

Next Topic | Previous Topic | Home

Last built: May 25, 2012