Completing a Business Layer with Windows Workflow Foundation
(Page 1 of 4 )
In this conclusion to a three-part article, we'll finish our discussion of how Windows Workflow Foundation can help us create an important layer of an application. It is excerpted from chapter four of the book
Building a Web 2.0 Portal with ASP.NET 3.5, written by Omar Al Zabir (O'Reilly, 2008; ISBN: 0596510500). Copyright © 2008 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.
Adding a New Tab (AddNewTabWorkflow)
Adding a new tab is quite simple, requiring only two steps, after the GUID is assigned (see Figure 4-7):
- Create a new blank page.
- Update the user settings and set the new page as the current page.
Moving Widgets (MoveWidgetInstanceWorkflow)
To move a widget, you must do the following (see Figure 4-8):
- Ensure the current user who is calling the workflow owns the widget instance.
- Fetch the widget instance and put in workflow context so that other activities can use it.
- Pull the widget up from its previous position, which means all the widgets below are shifted up.
- Push the widget onto its new position so that all widgets on the new column move down.
- Update the widget’s position.
Figure 4-7. AddNewTabWorkflow design view

Figure 4-8. MoveWidgetInstanceWorkflow design view
MoveWidgetInstanceWorkflow verifies whether the widget being moved is really the current user’s widget. This is necessary to prevent malicious web service hacking (see the “Implementing Authentication and Authorization” section in Chapter 3). TheEnsureOwnerActivitycan check both the page and the widget’s ownership (see Example 4-24).
Example 4-24. EnsureOwnerActivity Execute function
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
var db = DatabaseHelper.GetDashboardData();
if( this.PageId == 0 && this.WidgetInstanceId == 0 )
{
throw new ApplicationException("No valid object specified to check");
}
if( this.WidgetInstanceId > 0 )
{
// Gets the user who is the owner of the widget. Then sees if the current user is the
same.
var ownerName = (from wi in db.WidgetInstances
where wi.Id == this.WidgetInstanceId
select wi.Page.AspnetUser.LoweredUserName).First();
if( !this.UserName.ToLower().Equals( ownerName ) )
throw new ApplicationException(string.Format("User {0} is not the owner of the
widget instance {1}", this.UserName, this.WidgetInstanceId));
}
if( this.PageId > 0 )
{
// Gets the user who is the owner of the page. Then sees if the current user is the
same.
var ownerName = (from p in db.Pages
where p.ID == this.PageId
select p.AspnetUser.LoweredUserName).First();
if( !this.UserName.ToLower().Equals( ownerName ) )
throw new ApplicationException(string.Format("User {0} is not the owner of the page
{1}", this.UserName, this.PageId));
}
return ActivityExecutionStatus.Closed;
}
EnsureOwnerActivitytakesUserNameand eitherWidgetInstanceIdorPageId and verifies the user’s ownership. It should climb through the hierarchy fromWidgetInstanceto thePageand then toAspnetUser to check whether the username matches or not. If the username is different than the one specified, then the owner is different and it’s a malicious attempt.
CheckingPageownership requires just going one level up toAspnetUser. But checkingWidgetInstanceownership requires going up to the container page and then checking ownership of the page. This needs to happen very fast because it is called on almost every operation performed onPage orWidgetInstance. This is why you want to make sure it does a scalar select only, which is faster than selecting a full row.
Once the owner has been verified, the widget can be placed on the right column. The next activity,PutWidgetInstanceInWorkflow, does nothing but put theWidgetInstanceobject into a public property according to its ID so the object can be manipulated directly. The other activities in the workflow work with the object’sColumnNoandOrderNoproperties. The next step,PushWidgetsDownInNewColumn, calls thePushDownWidgetsOnColumnActivity, which pushes widgets down one row so there’s a room for a new widget to be dropped (see Example 4-25).
Example 4-25. PushDownWidgetsOnColumnActivity Execute function
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
var db = DatabaseHelper.GetDashboardData();
var query = from wi in db.WidgetInstances
where wi.PageId == PageId && wi.ColumnNo == ColumnNo && wi.OrderNo >= Position
orderby wi.OrderNo
select wi;
List<WidgetInstance>list = query.ToList();
int orderNo = Position+1;
foreach( WidgetInstance wi in list )
{
wi.OrderNo = orderNo ++;
}
db.SubmitChanges();
return ActivityExecutionStatus.Closed;
}
The idea is to move all the widgets right below the position of the widget being dropped and push them down one position. Now we have to update the position of the dropped widget using the activityChangeWidgetInstancePositionActivity(see Example 4-26).
Example 4-26. ChangeWidgetInstancePositionActivity Execute function
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
WidgetInstance widgetInstance = DatabaseHelper.GetDashboardData().WidgetInstances.
Single( wi => wi.Id == WidgetInstanceId );
DatabaseHelper.Update<WidgetInstance>( widgetInstance, delegate( WidgetInstance wi )
{
wi.ColumnNo = ColumnNo;
wi.OrderNo = RowNo;
});
return ActivityExecutionStatus.Closed;
}
The widget is placed on a new column, and the old column has a vacant place. But now we need to pull the widgets one row upward on the old column.ReorderWidgetInstanceOnColumnActivityfixes row orders on a column, eliminating the gaps between them (see Example 4-27). The gap in the column will be fixed by recalculating the row number for each widget on that column, starting from zero.
Example 4-27. ReorderWidgetInstanceOnColumnActivity Execute function
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
var db = DatabaseHelper.GetDashboardData();
var query = from wi in db.WidgetInstances
where wi.PageId == PageId && wi.ColumnNo == ColumnNo
orderby wi.OrderNo
select wi;
List<WidgetInstance>list = query.ToList();
int orderNo = 0;
foreach( WidgetInstance wi in list )
{
wi.OrderNo = orderNo ++;
}
db.SubmitChanges();
return ActivityExecutionStatus.Closed;
}
That’s all that is required for a simple drag-and-drop operation.
Next: Implementing the DashboardFacade >>
More .NET Articles
More By O'Reilly Media