2. Session Data Storage
The Surveys application must maintain some state data for each user as they design a survey. This section describes the design and implementation of user state management in the Surveys application.
Note:
The Surveys application must maintain session state while a user designs a survey.
2.1. Goals and Requirements
When a user designs a new survey in the Surveys application, they create the survey and then add questions one-by-one to the survey until it’s complete. Figure 1 shows the sequence of screens when a user creates a survey with two questions.
As you can see in the diagram,
this scenario involves two different screens that require the
application to maintain state as the user adds questions to the survey.
The developers at Tailspin considered three options for managing the
session state:
Use JavaScript and
manage the complete workflow on the client. Then use Ajax calls to send
the survey to the server after it’s complete.
Use the standard, built-in Request.Session
object to store the intermediate state of the survey while the user is
creating it. Because the Tailspin web role will run on several node
instances, Tailspin cannot use the default, in-memory session state
provider, and would have to use another storage provider such as the TableStorageSessionStateProvider from the Windows Azure samples.
Use an approach similar to ViewState that serializes and deserializes the workflow state and passes it between the two pages.
Note:
You can download the Windows Azure samples that include the TableStorageSessionStateProvider from http://code.msdn.microsoft.com/windowsazuresamples.
2.2. The Solution
You can compare the three
options using several different criteria. Which criteria are most
significant will depend on the specific requirements of your
application.
2.2.1. Simplicity
Something that is simple to
implement is also usually easy to maintain. The first option is the most
complex of the three, requiring JavaScript skills and good knowledge of
an Ajax library. It is also difficult to unit test. The second option
is the easiest to implement because it uses the standard ASP.NET Session object. Using the session state provider is simply a matter of “plugging-in” the TableStorageSessionStateProvider in the Web.config file. However, after the TableStorageSessionStateProvider finishes using the state data,
it does not automatically clean it up, so you would have to implement
this functionality. The third option is moderately complex, but you can
simplify the implementation by using some of the features in the ASP.
NET MVC 2 Futures package. Unlike the second option, it doesn’t require
any server side setup or configuration other than including the MVC
Futures code in the solution.
Note:
You can download the ASP.NET MVC 2 Futures code from http://aspnet.codeplex.com/releases/view/41742.
2.2.2. Cost
The first option has the lowest
costs because it uses a single POST message to send the completed
survey to the server. The second option has moderate costs that arise
from the table storage transaction costs incurred whenever the
application writes session state to or reads session state from table
storage. The amount of storage required is not likely to be significant.
Tailspin could estimate these costs based on the expected number
questions created per day and the average size of the questions. The
third option has higher costs than the second option because its costs
arise from bandwidth usage. Again, Tailspin can estimate the costs based
on the expected number of questions created per day and the average
size of the questions.
With both the second and third options, the data is encoded as Base64, so any estimate of the average question size must consider this. |
2.2.3. Performance
The first option offers the best performance
because the client performs almost all the work with no roundtrips to
the server until the final POST message containing the complete survey.
The second option will introduce some latency into the application; the
amount of latency will depend on the number of concurrent sessions, the
amount of data in the session
objects, and the latency between the web role and Windows Azure table
storage. The third option will also introduce some latency because each
question will require a round-trip to the server and each HTTP request
and response message will include all the current state data.
2.2.4. Scalability
All three options scale
well. The first option scales well because it doesn’t require any
session state data outside the browser, the second and third options
scale well because they are “web-farm friendly” solutions that you can
deploy on multiple web roles.
2.2.5. Robustness
The first option is the least
robust, relying on “fragile” JavaScript code. The second option uses
sample code that is not production quality, although you could enhance
it. The third option is the most robust, using easily testable
server-side code.
2.2.6. User Experience
The first option provides the
best user experience because there are no postbacks during the survey
creation process. The other two options require a postback for each
question.
2.2.7. Security
The first two options offer good security.
With the first option, the browser holds all the survey in memory until
the survey creation is complete, and with the second option, the
browser just holds a cookie with a session ID, while Windows Azure table
storage holds the survey data. The third option is not so secure
because it simply serializes the data to Base64 without encrypting it.
It’s possible that sensitive data could “leak” during the flow between
pages.
Tailspin decided to use the
third option that passes the survey design data between the two pages
as a serialized object. Instead of using cookies, the application stores
the data in a hidden field in the form on the pages involved.
2.3. Inside the Implementation
Now is a good time to walk through the session data
storage implementation that Tailspin selected 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/.
The following code example shows how the Action methods in the SurveysController controller class in the TailSpin.Web project deserialize the data sent from the browser. Notice how the hidden Survey parameter has the Deserialize attribute from the ASP.NET MVC 2 Futures package applied.
[HttpGet]
public ActionResult New([Deserialize]Survey hiddenSurvey)
{
if (hiddenSurvey == null)
{
hiddenSurvey = (Survey)this.TempData["hiddenSurvey"];
}
if (hiddenSurvey == null)
{
hiddenSurvey = new Survey(); // First time to the page
}
var model = new TenantPageViewData(hiddenSurvey);
model.Title = "New Survey";
return this.View(model);
}
[HttpPost]
public ActionResult New(Survey contentModel, [Deserialize]Survey
hiddenSurvey)
{
contentModel.Questions = hiddenSurvey.Questions;
if (!this.ModelState.IsValid)
{
var model = new TenantPageViewData<Survey>(contentModel);
model.Title = "New Survey";
return this.View(model);
}
contentModel.Tenant = this.TenantName;
try
{
this.surveyStore.SaveSurvey(contentModel);
}
catch (DataServiceRequestException ex)
{
...
}
return this.RedirectToAction("Index");
}
The following code example from the NewQuestion.aspx view shows how the application causes the form data from the client to be serialized by using the Html.Serialize element.
<% using (Html.BeginForm("AddQuestion", "Surveys")) {%>
<%: Html.ValidationSummary(true) %>
<%: Html.Serialize("hiddenSurvey")%>
<%: Html.Hidden("referrer", "addQuestion") %>
<dl>
<dt>
<%: Html.LabelFor(model => model.ContentModel.Text) %>
</dt>
<dd>
<%: Html.TextBoxFor(model => model.ContentModel.Text,
new { size = "60" })%>
...
<% } %>