Building Applications with Windows Workflow Foundation - Mapping User Actions to a Workflow
(Page 3 of 4 )
Each user action can be mapped to a workflow that responds to that action. For example, when a user wants to add a new widget, a workflow can take care of creating the widget, positioning it properly on the page, and configuring the widget with the default value. The first visit of a brand new user to the site is a complex operation, so it is a good candidate to become a workflow. This makes the architecture quite simple on the web layer—just call a workflow on various scenarios and render the UI accordingly, as illustrated in Figure 4-2.
Figure 4-2. User actions are mapped to a workflow. For example, when a user adds a new tab, the request goes to a workflow. The workflow creates a new tab, makes it current, configures tab default settings, adds default widgets, etc. Once done, the workflow returns success and the page shows the new tab.
Instead of using complex diagrams and lines of documentation to explain how to handle a particular user or system action, you can draw a workflow and write code inside it. This serves both as a document and a functional component that does the job. The next sections show scenarios that can easily be done in a workflow.
Dealing with First Visit by a New User (NewUserSetupWorkflow)
Handling the first visit of a brand new user is the most complex operation your web site will handle. It’s a good candidate for becoming a workflow. Figure 4-3 shows a workflow that does all the business layer work for the first-time visit and returns a complete page setup. The Default.aspx just creates the widgets as it receives them from the workflow and is not required to perform any other logic.
The operations involved in creating the first-visit experience for a new user are as follows:
- Create a new anonymous user
- Create two default pages
- Put some default widgets on the first page
- Construct a object model that contains user data, the user’s page collection, and the widgets for the first page
If you put these operations in a workflow, you get the workflow shown in Figure 4-3 .

Figure 4-3. New user visit workflow creates a new user account and configures the account with the default setup
The workflow takes the ASP.NET anonymous identification provider generated byUserNameas an input to the workflow from the Default.aspx page.
The first step in passing this input parameter to the workflow while running the workflow is to call theGetUserGuidActivityto get theUserIdfrom theaspnet_userstable for that user (see Example 4-17).
Example 4-17. GetUserGuidActivity Execute function
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
using( new TimedLog(UserName, "Activity: Get User Guid") )
{
var db = DatabaseHelper.GetDashboardData();
this.UserGuid = (from u in db.AspnetUsers
where u.LoweredUserName == UserName && u.ApplicationId == DatabaseHelper.
ApplicationGuid
select u.UserId).Single();
return ActivityExecutionStatus.Closed;
}
}
This activity is used in many places because it is a common requirement to get theUserIdfrom the username found from the ASP.NETContextobject. All the tables have a foreign key in theUserId column but ASP.NET gives only theUserName. So, in almost all the operations,UserNameis passed from the web layer and the business layer converts it toUserId and does its work.
Theusing(TimedLog)block records the execution time of the code inside theusingblock. It prints the execution time in the debug window as you read earlier in the “Performance Concerns with WF ” section.
The next step is to create the first page for the user usingCreateNewPageActivityshown in Example 4-18.
Example 4-18. CreateNewPageActivity Execute function
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
DashboardData db = DatabaseHelper.GetDashboardData();
var newPage = new Page();
newPage.UserId = UserId;
newPage.Title = Title;
newPage.CreatedDate = DateTime.Now;
newPage.LastUpdate = DateTime.Now;
db.Pages.Add(newPage);
db.SubmitChanges(ConflictMode.FailOnFirstConflict);
NewPageId = newPage.ID;
return ActivityExecutionStatus.Closed;
}
This activity takes theUserIDas input and produces theNewPageId property as output. It creates a new page, and default widgets are added on that page.CreateDefaultWidgetActivitycreates the default widgets on this page as shown in Example 4-19.
Example 4-19. CreateDefaultWidgetActivity Execute function
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
var db = DatabaseHelper.GetDashboardData();
var defaultWidgets = db.Widgets.Where( w => w.IsDefault == true ).ToList();
var widgetsPerColumn = (int)Math.Ceiling((float)defaultWidgets.Count/3.0);
var row = 0;
var col = 0;
foreach( Widget w in defaultWidgets )
{
var newWidget = new WidgetInstance();
newWidget.PageId= this.PageId;
newWidget.ColumnNo = col;
newWidget.OrderNo = row;
newWidget.CreatedDate = newWidget.LastUpdate = DateTime.Now;
newWidget.Expanded = true;
newWidget.Title = w.Name;
newWidget.VersionNo = 1;
newWidget.WidgetId = w.ID;
newWidget.State = w.DefaultState;
db.WidgetInstances.Add(newWidget);
row ++;
if( row >= widgetsPerColumn )
{
row = 0;
col ++;
}
}
db.SubmitChanges();
return ActivityExecutionStatus.Closed;
}
This is what needs to happen next:
- Decide how many widgets to add per column.
- Compute the number of widgets to put in each column so they have an even distribution of widgets based on the number of default widgets in the database.
- Run theforeachloop through each default widget and created widget instances.
- Create the second empty page.
- Call another workflow namedUserVisitWorkflow to load the page setup for the user. This workflow was used on both the first visit and subsequent visits because loading a user’s page setup is same for both cases.
TheInvokeWorkflowactivity that comes with WF executes a workflow asynchronously. So, if you are calling a workflow from ASP.NET that in turn calls another workflow, the second workflow is going to be terminated prematurely instead of executing completely. This is because the workflow runtime will execute the first workflow synchronously and then finish the workflow execution and return. If you useInvokeWorkflowactivity to run another workflow from the first workflow, it will start on another thread, and it will not get enough time to execute completely before the parent workflow ends, as shown in Figure 4-4.
Figure 4-4. InvokeWorkflow executes a workflow asynchronously, so if the calling workflow completes before the called workflow, it will terminate prematurely
So,InvokeWorkflowcould not be used to execute theUserVisitWorkflowfromNewUserSetupWorkflow. Instead it is executed using theCallWorkflow activity, which takes a workflow and executes it synchronously. It’s a handy activity I found on Jon Flanders’ blog (http://www.masteringbiztalk.com/blogs/jon/ PermaLink,guid,7be9fb53-0ddf-4633-b358-01c3e9999088.aspx).
The beauty of this activity is that it properly maps both inbound and outbound properties of the workflow that it calls, as shown in Figure 4-5.
TheUserNameproperty is passed from theNewUserVisitWorkflow, and it is returning theUserPageSetup, which contains everything needed to render the page for the user.
Next: Dealing with the Return Visit of an Existing User (UserVisitWorkflow) >>
More .NET Articles
More By O'Reilly Media