Filtering Data

patterns & practices Developer Center

On this page: Download:
You Will Learn | Overview of the Solution - Registering User Preferences, Identifying Which Devices to Notify, Selecting Surveys to Synchronize | Inside the Implementation - Storing Filter Data, Building a List of Devices to Receive Notifications, Building a List of Surveys to Synchronize with the Mobile Client Download code samples
Download book as PDF

You Will Learn

  • How to perform user preference registration in order to enable data filtering.
  • How to identify which Windows Phone devices to notify when new data is available.
  • How to identify which data to include in the synchronization process between the cloud and a Windows Phone device.

Users of the mobile client application can express preferences for which surveys they would like to see on their devices. In the application, the criteria will be a list of preferred tenants (survey creators such as Adatum and Fabrikam in the sample). There are two scenarios in which the Tailspin Surveys service must filter data based on filter criteria from the client:

The Surveys service filters lists of surveys for notifications and for synchronization.

  • The cloud service can send notifications to mobile clients to tell them when new surveys are available. The notification service should only send a notification to a user if one of the user's preferred tenants created the new survey.
  • When the mobile client synchronizes its data with the Tailspin Surveys service, the phone should only receive surveys created by the user's preferred tenants.

Note

For more details about the notification process, see the section, "Notifying the Mobile Client of New Surveys," earlier in this chapter. For more details about the synchronization process, see the section, "Synchronizing Data between the Client and the Cloud," in Chapter 3, "Using Services on the Phone."

Overview of the Solution

There are three tasks that make up the filtering functionality in the Tailspin Surveys application: registering the user's preferences; identifying which devices to notify when a tenant adds a new survey; and identifying which surveys to include in the synchronization process.

Registering User Preferences

The user creates a list of preferred tenants on the FilterSettingsView page in the mobile client application. When the user saves the settings, the Tailspin Surveys mobile client application sends the preferences to the Tailspin Surveys registration service that then stores the list of preferred tenants in Windows Azure table storage.

Figure 5 outlines the way that the mobile client application saves the user's settings in the Tailspin Surveys web service.

Hh821033.2B57C27F663721B67E0F0C46097958F0(en-us,PandP.10).png

Figure 5

Saving user settings in the Tailspin Surveys web service

The view model uses a service class to send the settings to the Tailspin Surveys registration web service. All the settings are encapsulated in a data transfer object that the web service code unpacks and saves in Windows Azure table storage.

The following table shows the structure of the tenant filter table in Windows Azure table storage that stores the list of preferred tenants for each user.

Column

Notes

Tenant name

A string holding the tenant name. This column is the table's partition key.

User name

A string holding the user name. This column is the table's row key.

The application can use this table to retrieve a list of tenants that a particular user is interested in or a list of users who are interested in a particular tenant.

Hh821033.note(en-us,PandP.10).gifMarkus Says:
Markus
                If you query this table and include the tenant name in the <strong>where</strong> clause, the query will run efficiently because it only needs to access a single partition. For example, finding all the users who are interested in surveys created by Adatum is an efficient query. However, asking for a list of all the tenants that a particular user is interested in is an inefficient query because it must scan all the partitions that make up the table.</td>

Note

For more information about Windows Azure table storage, including partition keys and row keys, see Chapter 5, "Phase 2: Automating Deployment and Using Windows Azure Storage," of the book, Moving Applications to the Cloud 2nd Edition. It is available on MSDN.

The following table shows the structure of the user device table in Windows Azure table storage that records which physical devices, identified by a unique URI allocated by the MPNS, are associated with each user.

Column

Notes

User name

A string holding the user name. This column is the table's partition key.

Device URI

A string holding the device's unique URI that the MPNS uses to push notifications to the device. This column is the table's row key.

The structure of this table reflects the fact that a user can have more than one device, each with a unique URI for the MPNS to use. Tailspin optimized this table to support queries that look for the list of devices owned by a user.

Note

With the current implementation, it's possible for a user who owns multiple Windows Phone devices to enter different lists of preferred tenants on the different devices. The data model in the Tailspin Surveys service only supports a single list of preferred tenants for each user, so the user might see unexpected results on some of their devices.

Identifying Which Devices to Notify

Figure 6 outlines the process that the Tailspin Surveys service uses to identify which devices to notify when a subscriber adds a new survey.

Hh821033.9185C02BD3C4C83671DD4333E65B3EB1(en-us,PandP.10).png

Figure 6

Building a list of devices to notify about a new survey

Identifying which devices to notify when a tenant adds a new survey requires two queries. The first query gets the list of users who are interested in that particular survey and this query uses a filtering service to build this list of users based on user preferences stored in Windows Azure table storage.

Hh821033.note(en-us,PandP.10).gifJana Says:
Jana
                In the current version of the application, obtaining the list of users who are interested in a particular survey uses just a single criterion: the user's list of preferred tenants. In the future, the criteria may become more complex if, for example, users can express preferences for location, survey length, or some other attribute of the survey. The developers at Tailspin have implemented an extensible filtering mechanism to accommodate any future additions to the list of supported criteria.</td>

The second query uses the list of users to find a list of devices to notify, again using data stored in Windows Azure table storage that maps users to devices.

Selecting Surveys to Synchronize

Figure 7 outlines the way that the Tailspin application delivers new, relevant surveys to the mobile client application when it synchronizes with the Tailspin Surveys service. The user initiates the synchronization process from the SurveyListView page, and the view model invokes a method in a service class to retrieve the list of new surveys. Alternatively, the synchronization process can be automatically initiated by the background agent, which periodically runs if the user has enabled background tasks in the AppSettingsView page. In the Tailspin Surveys service, the application gets the list of surveys to return to the client from a filtering service class.

Follow link to expand image

Figure 7

Building a list of new surveys for a user

Identifying which surveys to download when a user synchronizes from the mobile client requires two queries. The first query returns a list of tenants based on user preferences stored in Windows Azure table storage. In the current version of the application, these preferences are lists of tenants in which the user is interested. The second query, which is inside the filter, uses the list of tenants to return a list of new surveys from those tenants.

Inside the Implementation

Now let's take a more detailed tour of the code that implements the filtering of the list of surveys. As you go through this section, you may want to download the Windows Phone Tailspin Surveys application from the Microsoft Download Center.

Storing Filter Data

The Tailspin Surveys service stores filter data in Windows Azure table storage. It uses one table to store the tenant filter data, and one table to store the device filter data. The following code example shows how the TenantFilterStore class saves tenant filter data in the Windows Azure table storage.

public class TenantFilterStore : ITenantFilterStore
{
  private readonly IAzureTable<TenantFilterRow> tenantFilterTable;
  ...
  public void SetTenants(string username, IEnumerable<string> tenants)
  {
    var rowsToDelete = (from r in this.tenantFilterTable.Query
      where r.RowKey == username
      select r).ToList();

    this.tenantFilterTable.Delete(rowsToDelete);

    var rowsToAdd = tenants.Select(t => new TenantFilterRow
    { 
      PartitionKey = t, 
      RowKey = username 
    });
    this.tenantFilterTable.Add(rowsToAdd);
  }
}
Hh821033.note(en-us,PandP.10).gifMarkus Says:
Markus The application removes any existing data with the same key before adding the new rows.

The following code example shows how the UserDeviceStore class saves device data.

public class UserDeviceStore : IUserDeviceStore
{
  private readonly IAzureTable<UserDeviceRow> userDeviceTable;
  ...
  public void SetUserDevice(string username, Uri deviceUri)
  {
    this.RemoveUserDevice(deviceUri);

    var encodedUri = Convert.ToBase64String(
      Encoding.UTF8.GetBytes(deviceUri.ToString()));
    this.userDeviceTable.Add(new UserDeviceRow
    {
      PartitionKey = username,
      RowKey = encodedUri
    });
  }

  public void RemoveUserDevice(Uri deviceUri)
  {
    var encodedUri = Convert.ToBase64String(
      Encoding.UTF8.GetBytes(deviceUri.ToString()));
    var row = (from r in this.userDeviceTable.Query
      where r.RowKey == encodedUri
      select r).SingleOrDefault();

    if (row != null)
    {
      this.userDeviceTable.Delete(row);
    }
  }
}

The following code example shows how the SetFilters web method in the RegistrationService class stores the list of preferred tenants sent from the mobile client.

public void SetFilters(SurveyFiltersDto surveyFiltersDto)
{
  var username = Thread.CurrentPrincipal.Identity.Name;

  this.tenantFilterStore.SetTenants(username, 
    surveyFiltersDto.Tenants.Select(t => t.Key));
}

Note

There is also a GetFilters web method to return the current list of preferred tenants to the client. The phone does not save the filter information locally; instead, it retrieves it from the web service using the GetFilters method when it needs it.

Building a List of Devices to Receive Notifications

Whenever a tenant creates a new survey, the Tailspin Surveys service must send notifications to all the devices that are interested in that survey. The criteria that the service uses to select the devices may change in future versions of the application, so the developers at Tailspin implemented an extensible filtering method to build the list.

The following code example from the Run method in the NewSurveyNotificationCommand class retrieves a list of device URIs to which it will send notifications. The NewSurveyNotificationCommand class implements a command that a Windows Azure worker role executes in response to receiving a message on a Windows Azure queue.

var deviceUris =
  from user in this.filteringService.GetUsersForSurvey(survey)
  from deviceUri in this.userDeviceStore.GetDevices(user)
  select deviceUri;

This code uses the FilteringService class to get a list of users who are interested in the new survey, and then it uses the GetDevices method in the UserDeviceStore class to get a list of device URIs for those users. The application populates the user device store when the mobile client registers for notifications with the MPNS. For more information, see the section, "Notifying the Mobile Client of New Surveys," earlier in this chapter.

The following code example shows how the FilteringService class uses one or more ISurveyFilter instances in the GetUsersForSurvey method to filter the list of users.

private readonly ISurveyFilter[] filters;
...
public IEnumerable<string> GetUsersForSurvey(Survey survey)
{
  return (from filter in this.filters
    from user in filter.GetUsers(survey)
    select user).Distinct();
}

The current version of the application uses a single filter class named TenantFilter to retrieve a list of users from the tenant filter store based on the tenant name. The following code example shows part of this TenantFilter class.

public class TenantFilter : ISurveyFilter
{
  ...
  private readonly ITenantFilterStore tenantFilterStore;
  ...
  public IEnumerable<string> GetUsers(Survey survey)
  {
    return this.tenantFilterStore.GetUsers(survey.Tenant);
  }
}

Building a List of Surveys to Synchronize with the Mobile Client

Whenever the synchronization process is initiated, the Tailspin Surveys service must build a list of new surveys that the user is interested in. The current version of the application uses the user's list of preferred tenants to select the list of surveys, but Tailspin may expand the set of selection criteria in the future. The mobile client invokes the GetSurveys web method in the SurveysService class to get a list of new surveys. The GetSurveys method uses the GetSurveysForUser method of the FilteringService class to get the list of surveys that it will return to the client.

The following code example shows how the FilteringService class gets the list of surveys for a user.

private readonly ISurveyFilter[] filters;
...
public IEnumerable<Survey> GetSurveysForUser(
  string username, DateTime fromDate)
{
  return (from filter in this.filters
    from survey in filter.GetSurveys(username, fromDate)
    select survey).Distinct(new SurveysComparer());
}

This method can use one or more ISurveyFilter objects to filter the list of surveys. The current version of the application uses a single filter named TenantFilter. The following code example shows the GetSurveys method of the TenantFilter class that first retrieves a list of tenants for the user, and then retrieves a list of surveys for those tenants.

public IEnumerable<Survey> GetSurveys(string username, DateTime fromDate)
{
  var tenants = this.tenantFilterStore.GetTenants(username);
  if (tenants.Count() == 0)
  {
    tenants = this.tenantStore.GetTenants().Select(t =>
      t.Name.ToLowerInvariant());
  }

  return (from tenant in tenants
    from survey in this.surveyStore.GetSurveys(tenant, fromDate)
    select survey).Distinct(new SurveysComparer());
}

Next Topic | Previous Topic | Home

Last built: May 25, 2012