This section describes several interesting scenarios
in the Surveys application where the application displays data to users
and how the underlying data model supports this functionality.
1. Paging through Survey Results
The owner of a survey must be able to browse through the survey results, displaying a single survey
response at a time. This feature is in addition to being able to view
summary statistical data, or being able to analyze the results using SQL
Azure. The Surveys application contains a Browse Responses page for
this function.
1.1. Goals and Requirements
The design of this feature
of the application must address two specific requirements. The first
requirement is that the application must display
the survey responses in the order that they were originally submitted.
The second requirement is to ensure that this feature does not adversely
affect the performance of the web role.
1.2. The Solution
The developers at Tailspin
considered two solutions, each based on a different storage model. The
first option assumed that the application stored the survey response data in table storage. The second option, which was the one chosen, assumed that the application stored the survey response data in BLOB storage.
1.2.1. Paging with Table Storage
The developers at Tailspin
looked at two features of the Windows Azure table storage API to help
them design this solution. The first feature is the continuation token
that you can request from a query that enables you to execute a
subsequent query that starts where the previous query finished. You can
use a stack data
structure to maintain a list of continuation tokens that you can use to
go forward one page or back one page through the survey responses. You
must then keep this stack of continuation tokens in the user’s session
state to enable navigation for the user.
The second useful API
feature is the ability to run asynchronous queries against Windows Azure
table storage. This can help avoid thread starvation in the web
server’s thread pool in the web role by offloading time-consuming tasks
to a background thread.
1.2.2. Paging with Blob Storage
The assumption behind this
solution is that each survey answer is stored in a separate BLOB. To
access the BLOBs in a predefined order, you must maintain a list of all
the BLOBs. You can then use this list to determine the identity of the
previous and next BLOBs in the sequence and enable the user to navigate
backward and forward through the survey responses.
To support alternative orderings of the data, you must maintain additional lists.
1.2.3. Comparing the Solutions
The previous section,
which discusses alternative approaches to saving survey response data,
identified lower transaction costs as the key advantage of saving
directly to BLOB storage instead of using a delayed write pattern to
save to table storage. Paging with table storage is complex because you must manage the continuation stack in the user’s session state.
The obvious solution (in this case to use table storage) is not always the best solution. |
However, you must consider
the costs and complexity associated with maintaining the ordered list of
BLOBs in the second of the two alternative solutions. This incurs two
additional storage transactions for every new survey; one as the list it
retrieved from BLOB storage, and one as it is saved back to BLOB
storage. This is still fewer transactions per survey response than the
table-based solution. Furthermore, it’s possible to avoid using any
session state by embedding the links to the next and previous BLOBs
directly in the web page.
1.3. Inside the Implementation
Now is a good time to walk through the data
paging functionality that Tailspin implemented in more detail. As you
go through this section, you may want to download the Visual Studio
solution for the Tailspin Surveys application from http://wag.codeplex.com/.
This walkthrough is
divided into two sections. The first section describes how the
application maintains an ordered list of BLOBs. The second section
describes how the application uses this list to page through the
responses.
1.3.1. Maintaining the Ordered List of Survey Responses
The surveys application already uses an asynchronous task in a worker role to calculate the summary statistical data
for each survey. This task periodically process new survey answers from
a queue, and as it processes each answer, it updates the ordered list
of BLOBs containing survey results. The application assigns each BLOB an
ID that is based on the tick count when it is saved, and the
application adds a message to a queue to track new survey responses.
Note:
Surveys uses an asynchronous task in a worker role to maintain the ordered list of BLOBs.
The following code example from the SurveyAnswerStore
class shows how the application creates a BLOB ID, saves the BLOB to
the correct BLOB container for the survey, and adds a message to the
queue that tracks new survey responses.
public void SaveSurveyAnswer(SurveyAnswer surveyAnswer)
{
var surveyBlobContainer = this.surveyAnswerContainerFactory
.Create(surveyAnswer.Tenant, surveyAnswer.SlugName);
surveyBlobContainer.EnsureExist();
DateTime now = DateTime.UtcNow;
surveyAnswer.CreatedOn = now;
var blobId = now.GetFormatedTicks();
surveyBlobContainer.Save(blobId, surveyAnswer);
this.surveyAnswerStoredQueue.AddMessage(
new SurveyAnswerStoredMessage
{
SurveyAnswerBlobId = blobId,
Tenant = surveyAnswer.Tenant,
SurveySlugName = surveyAnswer.SlugName
});
}
The Run method in the UpdatingSurveyResultsSummaryCommand class in the worker role calls the AppendSurveyAnswerIdToAnswerList method for each survey response in the queue of new survey responses.
The following code example shows how the AppendSurveyAnswerIdToAnswerList method in the SurveyAnswerStore class.
public void AppendSurveyAnswerIdToAnswersList(string tenant,
string slugName, string surveyAnswerId)
{
string id = string.Format(CultureInfo.InvariantCulture,
"{0}-{1}", tenant, slugName);
var answerIdList = this.surveyAnswerIdsListContainer.Get(id)
?? new List<string>(1);
answerIdList.Add(surveyAnswerId);
this.surveyAnswerIdsListContainer.Save(id, answerIdList);
}
The application stores list of survey responses in a List object, which it serializes in the JSON format and stores in a BLOB. There is one BLOB for every survey.
The
application adds new responses to the queue in the order that they are
received. When it retrieves messages from the queue and adds the BLOB
IDs to the list, it preserves the original ordering. |
1.3.2. Implementing the Paging
When the Surveys
application displays a survey response, it finds the BLOB that contains
the survey response by using a BLOB ID. It can use the ordered list of BLOB IDs to create navigation links to the next and previous survey responses.
The following code example shows the BrowseResponses action method in the SurveysController class in the TailSpin.Web project.
public ActionResult BrowseResponses(string tenant,
string surveySlug, string answerId)
{
SurveyAnswer surveyAnswer = null;
if (string.IsNullOrEmpty(answerId))
{
answerId = this.surveyAnswerStore
.GetFirstSurveyAnswerId(tenant, surveySlug);
}
if (!string.IsNullOrEmpty(answerId))
{
surveyAnswer = this.surveyAnswerStore
.GetSurveyAnswer(tenant, surveySlug, answerId);
}
var surveyAnswerBrowsingContext = this.surveyAnswerStore
.GetSurveyAnswerBrowsingContext(tenant,
surveySlug, answerId);
var browseResponsesModel = new BrowseResponseModel
{
SurveyAnswer = surveyAnswer,
PreviousAnswerId =
surveyAnswerBrowsingContext.PreviousId,
NextAnswerId = surveyAnswerBrowsingContext.NextId
};
var model = new TenantPageViewData<BrowseResponseModel>
(browseResponsesModel);
model.Title = surveySlug;
return this.View(model);
}
This action method uses the GetSurveyAnswer method in SurveyAnswerStore class to retrieve the survey response from BLOB storage and the GetSurveyAnswerBrowsingContext method to retrieve a SurveyBrowsingContext object that contains the BLOB IDs of the next and previous BLOBs in the sequence. It then populates a model object with this data to forward on to the view.