Professional Documents
Culture Documents
1.Introduction.
One of the greatest innovations in Windows Sharepoint Service v3 and MOSS 2007 is the integration
with Windows Workflow Foundation, a core component of the .Net Framework 3.0.
To have a general overview of Windows Workflow Foundation, take a look at these interesting articles
from David Chappell.
Understanding Workflow in Windows Sharepoint Services and the 2007 Microsoft Office System.
In this long series of articles, we will provide a step by step approach to building workflows in Sharepoint
with both Microsoft Visual Studio and Microsoft Sharepoint designer.
The workflow we are going to create step by step is an application that allows users to submit their
expense reports; as soon as their expense report will be submitted, a workflow will be activated; this
workflow will:
Before starting, make sure you have a team site with the following list:
(very important , make sure the Manager and Manager Of columns are of type Person or Group with
the Show Field option as account )
Before starting Visual Studio 2005, make sure the followings components have been installed:
select Sharepoint project type and the Sequential Workflow Library template. Name the project
U2U.ExpenseReport:
In the solution Explorer, delete Workflow1.cs, and add a new workflow file:
Select sequential Workflow and name the file ExpenseReportWorkflow.cs:
Most activities are either activities derived from HandleExternalEventActivity which is a basic Workflow
Foundation activity waiting for an Event or CallExternalMethod which is an activity calling a class
implementing an interface (this class is called “local service”).
The communication between the WorkflowRuntime host (which in our case is Sharepoint) and the
workflow follows the usual pattern defined by the Windows Workflow Foundation team:
• ISharepointService
• IListItemServices
• ITaskServices
• IWorkflowModificationservice
For Windows Workflow Foundation aficionados, it’s worth knowing that the WorkflowRuntime class is
completely encapsulated and hidden by the Sharepoint framework; therefore we cannot add our own local
/runtime services and invoke them from our custom activities as we usually do we host the workflow
runtime ourselves.
To display the Sharepoint activities in Visual Studio Toolbox, let’s create a new tab: “Sharepoint
activities” and drag & drop the Microsoft.sharepoint.WorkflowActions.dll on it (or use the toolbox
browse menu, but the first option is faster).
Many others activities are also available in the WorkflowActions assembly but their ToolboxItem attribute
is set to false so that they won’t show up in the toolbox but they can be used with Microsoft Sharepoint
Designer.
To make sure our workflow will really work, let’s drag and drop a LogToListHistory activity after the
onWorkflowActivated1 activity.
Set its HistoryOutcome property to “hello from Serge”:
It’s time to deploy our hello world workflow: workflows in Sharepoint must be deployed as features, so
we need to provide more details about our current feature in the feature.xml file .
Make sure the features code snippet are activated (the snippets are installed on C:\Program
Files\Microsoft Visual Studio 8\Xml\1033\Snippets\Windows SharePoint Services Workflow, press CTL
K + B to add this folder to the snippets; choose XML as the language).
Select the feature.xml file and use the feature snippet to insert the feature code:
Replace the Guid with a new Guid (use the create Guid tool in Visual Studio Tools Menu-Create Guid
menu, select registry format, click on “New Guid” Button, click on Copy and paste it in the feature.xml
file).
Replace the Title and Description attributes with some meaningful information:
Sign the assembly: project properties-Signing- Check sign the assembly, select new, create a new file and
rebuild the project.
The manifest file of the feature is workflow.xml: replace the existing content with the tags provided by
the workflow snippet; set the Id with a generating a new Guid, provide a Name, a Description, and
specify the CodeBesideClass.
Use Reflector to retrieve your assembly strong name and insert it into the CodeBesideAssembly attribute :
The install.bat file will deploy our workflow by registering the assembly to the Gac by will installing and
activating our feature to the site collection.
We need to specify the site collection in the install.bat file: replace “http://localhost “ string with your site
collection url :
In my version of the install.bat, 3 occurrences have been replaced.
Next we need to specify our assembly name (that will also be our feature name here): replace the string
“MyFeature” with the string “U2U.ExpenseReport”.
In my version of the install.bat file, 13 occurrences have been replaced. Save the file.
You can verify your workflow has been activated: Site settings Menu-Galleries-Workflows
Let’s make an association between our ExpenseReports list and the workflow :
Select the ExpenseReports list, go the List Settings: in Permission and Management, select Workflow
settings:
Select Add a workflow and select the Expense report Workflow ; type a unique name for the association
like ApproveReject :
Click on Ok.
Normally a new column the association has been added to the list; if everything is ok, the workflow status
for the item is completed:
If you click on the Completed hyperlink, you’ll be redirected to the workflow history and you’ll see our
message:
Congratulations ! In the next article we will implement the Submit expense Report scenario.
Step 2: Extending the workflow: checking the amount and setting the status
Download the code
The scenario
What we want to implement here is retrieving the list item : the expense report informations; if the
expense report is greater than 1000 then the manager approval will be required, otherwise the expense
report will be accepted.
This is straightforward in Sharepoint : we just need to bind the OnWorkflowActivated activity with a
workflow member:
Select its workflowProperties in the property page and Click on the ellipsis button,
Select Create Field, and set Workflowproperties as the new field name:
A new Data member of type Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties will be
generated and linked to the activity information. Among others this class will provide access to the list
item via its Item property.
if (WorkflowProperties.Item["Amount"] != null)
float.TryParse(WorkflowProperties.Item["Amount"].ToString(), out
Amount);
if (WorkflowProperties.Item["Description"] != null)
Description = WorkflowProperties.Item["Description"].ToString();
if (WorkflowProperties.Item["Status"] != null)
Status = WorkflowProperties.Item["Status"].ToString();
//Submitted By contains a space we need to use its internal name
//WorkflowProperties.Originator is the submitter
WorkflowProperties.Item["Submitted_x0020_By"] =
WorkflowProperties.Originator.ToString();
Let’s compile your changes and call the install.bat file. Call your web site to start the w3p.exe process.
Set a breakpoint in the onWorkflowActivated_Invoked event handler and attach the assembly to the
w3p.exe process (Debug menu-Attach to Process, select w3p.exe).Go to the ExpenseReports list , start
the workflow until you hit your breakpoint in the debugger and watch your data members :
Checking the values and setting the status
Drag and drop an ifElse activity and rename the activities as following:
In the ifSmallAmount activity, select the Declarative Rule Condition for the condition property:
Enter SmallAmount as the ConditionName :
Click on the ellipsis (…) button and the rule editor will show up.
conditions. The language for these expressions is CodeDom. We’ll get back to this
Double click on the requestManagerApproval activity and associate it with the following code :
Step 3: Extending the workflow: finding the manager and creating a custom
activity
The Scenario :
Make sure the Sharepoint object model namespace has been specified in ExpensereportWorkflow.cs:
using Microsoft.SharePoint;
Build the project, call install.bat and test the workflow by logging in with the “Serge” account (Serge's
manager is Jim)
There are still some extra characters we need to remove in the FindManager function
if (managerItem[managerColumnName].ToString().Contains(managee))
{
…
manager = manager.Remove(0, manager.IndexOf('#') + 1);
return manager;
}
Build the project, call install.bat and test the workflow :
Creating a Custom activity
If we need to access the FindManager function in several workflows it make sense to encapsulate it in an
Custom activity.
Add a new Workflow project the solution : select the Workflow Activity Library template; name it
U2U.ManagerActivity:
using Microsoft.SharePoint
Define a public property for each data that can be know at design time :
• ManagerListTitle,
• ManagerColumnName,
• ManageeColumnName
We have to define a dependency property for each information that can only be retrieved at runtime:
• Managee
• WebSite
Make sure the snippet for dependency property is available, otherwise click on CTRL K+B , select Visual
C# and add the workflow folder :
Create another 2 other Dependency properties : Managee and Manager; keep string as the type.
return ActivityExecutionStatus.Closed;
}
Call the Findmanager function in Execute()
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
this.Manager = this.FindManager(
this.ManagerListTitle,
this.ManagerColumnName,
this.ManageeColumnName,
this.Managee,
this.WebSite);
return ActivityExecutionStatus.Closed;
}
}
If you display the ExpenseReportWorkflow in the Workflow Designer, the new Activity should show up
in the toolbox :
Select the WebSite property , click on the ellipsis button (…) and bind it to an existing member : the Web
property the WorkflowProperties field :
Select the Managee property and bind it to an existing member : the Originator property of the
workflowProperties member :
The activity will retrieve the manager and we will store this maneger into a new workflow property :
Set the copy local property of the reference to the U2UManagerActivity assembly to true:
Sign the new project, rebuild the solution and update install.bat in order to register the new assembly into
the Gac.
Make sure your account has a manager defined in the Managers list.
Build the project and test the workflow. If you get the error message "Failed on start", this probably
means that the activity assembly has not been registered into the Gac.
If the expense reports requires the manager approval, we want the manager to receive a task in his task
list in order to approve/reject the expense report.
Let’s drag and drop a CreateTask activity before the requestManagerApproval activity:
Set its Correlation token to ApproveRejectToken and the associated ownerActivityName to
ExpenseReportWorkflow:
Bind the Taskproperties property to a new member (click on he ellipsis button…):
Bind the TaskId to a new member (field): ApproveRejectTaskId.
Double click on the ApproveReject task to generate a new event handler and store a new Guid into
ApproveRejectTaskId, set the task title, and associate the task with the manager:
OnTaskChange activity
Any event OnTaskChanged coming form the task will be handled by the WaitForApprovalRejection
activity if we group them with the same correlation token (you ‘ll have a better understanding of the
correlation token in the next tutorial).
If we consider that when the user replies, the task should complete, then we need to drag and drop a
CompleteTask activity.
CompleteTask activity
Name it ApproveRejectTaskComplete.
Test
Rebuild the solution, call the install.bat and test your workflow : a new task should have neen assigned to
Jim:
The workflow status will be In progress which is a good indication that the workflow is waiting for an
event:
Then we need to create either Infopath forms (only on MOSS) or aspx form (on Wss v3 or on MOSS).
We need to create a Data Connection; select the Design Tasks panel, select Data Sources and click on
Manage Data Connections:
Add a new data connection; select Create a new connection to Submit data :
Click Next to select to the hosting environment… :
Click Next and name it Submit.
On the Design task panel click on Data Source, right click on myfields and add a new field; name it Status
an keep the text data type:
Click on the Accept button, click on the Rules button and on the Add button.
Click on the Add Action button, select the Set a field’s value Action, select the field status and set the
Value to Accepted :
Follow the same procedure for the Reject button , but set the Value to Rejected.
On both the Accept and Reject button, click on the control, select Rules, select rule1, click on the Modify
button: add a new action :
Add another action :
Don’t forget to follow the same procedure for the Reject button.
Set the Security Level of the form to Domain (menu Tools-Forms Option-Security and Trust) :
Publish the infopath form : File Menu-Publish: click ok to save the form and publish the form to a
newtwork location:
Click Next, save the form in your project directory with the name ApproveReject.xsn.
Click on Next and keep the text box empty (very important) :
Click Next until the Wizard closes.
Close Infopath.
Modify the manifest file (workflow.xml) in order to declare this form as the form to use to interact with
the task:
In this part of the tutorial we are going to use only one type of task to interact with the workflow, so
specify one task content type :
In the feature.xml file, add the following attributes to the feature element for handling the infopath form:
::Note: Uncomment these lines if you've modified your deployment xml files or IP forms
We can retrieve these value at the level of the workflow by using the AfterPropertries property of the
OnTaskChange activity :
The data coming from the Infopath form controls will be stored in its ExtendedProperties property which
is an Hashtable.
Rebuild the solution, call install.bat, test the workflow; click on the new task in the task list and the
Infopath Form should show up; submit your choice and check the item status.
Even if it’s not the best choice here, imagine that we split the approve/reject process in 2 tasks : an
approving task and a rejecting task: this means having 2 tasks forms and refactoring the workflow to
make it listening to 2 possible events (the manager did approve or the manager did reject).
Hands-on
Design reject.xsn , change the header and remove the accept button:
Publish this form as we did for the Reject form , but the form template should be Approve and the file
Approve.xsn.
Move the ApproveRejectTask to the left branch of the ParallelActivity and drag & drop another
CreateTask activity to the right branch.Rename these activities respectively approveTask and rejectTask;
set their correlation token to ApproveToken and RejectToken and their invoked method to
ApproveTask_MethodInvoking and to RejectTask_MethodInvoking:
Note: using a ParallelActivity is not strictly necessary here, we could have put the approveTask and the
rejectTask activities in a sequence.
Now we need to listen to the manager’s decision : 2 events could be sent: approved or rejected; the usual
pattern for handling this in Workflow Foundation is to use a ListenActivity:
Drag and drop a ListenActivity just after the ParallelActivity and move the WaitForApprovalRejection
and the ApproveRejectTaskCompletete to its left branch; make a copy these activities to the right branch.
Rename the left branch to WaitForApproval and the right branch to waitForRejection
Set the Invoked property of onTaskApprovalChanged to a new event handler :
WaitForApprovalTask_Invoked.
We now have to change the correlation token : the left branch activities will be correlated with
ApproveToken and the right branch activities with RejectToken.
The activities on each branch must use the same taskID : we need to create 2 new taskId : ApproveTaskId
and RejectTaskId at the level of respectively approveTask and rejectTask activities (taskId property –Bind
to a new member-Create field).
Set the taskId property of the left branch activities to approveTaskId and to rejectTaskId for the right
branch activities.
Since we’ll have 2 tasks, we need to stored the tasks data in 2 news TaskProperties ; bind the
Taskproperties property of the approveTask and rejectTask activities to 2 new taskproperties field
members : ApprovedTaskProperties and RejectTaskProperties
Changing the TaskProperties
Since we’ll have 2 tasks, we need to stored the tasks data in 2 news TaskProperties ; bind the
Taskproperties property of the approveTask and rejectTask activities to 2 new TaskProperties field
members : ApprovedTaskProperties and RejectTaskProperties .
The TaskType property will be used to link the forms to the appropriate tasks.
Let’s modify the workflow.xml file :modify the task0_FormURN to the approval form template Id and
add define a new task form (task1_FormURN) for the rejection form .
Double click on the onTaskApprovalChanged and on the onTaskRejectionChanged activities and set their
event handler as follows:
We need to improve the workflow design: indeed 2 tasks (Approve and Reject) will be created, and the
manager can only perform one of them: for instance, if the manager approves the expense report, then the
reject task should automatically be deleted.
Drag and drop 2 delete DeleteTask activities at the end of each branch of the ListenActivity:
Rename the DeleteTask of the left branch to deleteRejectionTask an and bind its TaskId property to the
existing field RejectTaskId; set its correlationToken to RejectToken.
Rename the DeleteTask of the right branch to deleteApprovalTask an and bind its TaskId property to the
existing field ApproveTaskId set its correlationToken to ApproveToken.
The following picture illustrates a important part of the workflow left branch:
Build the solution, call install.bat and test the workflow; you’ll notice that 2 tasks have been created; if
you select the reject task and if you click on the reject button in the form, then the Approve task will be
removed and vice-versa.
Scenario:
What we want to illustrate here is the initialization form; the current user has a manager defined in the
Managers table, therefore most of the time the workflow must be able to retrieve the manager; but in
some situations the manager could be in vacation ->the user needs to specify a temporary manager;
we can achieve this by defining an initialization form which is a form that will show up when the user
will actually start the workflow.
Hands-On
Name the field Account and rename the Data Source group to TemporyManager (right click on the data
source- properties).
Add a new submit data source (Design tasks-DataSource-Manage Data Connections-Add-Create a New
Connection to Submit Data-To the Hosting Environment…); name it Submit.
Set the security of the form to Domain and make sure the form can be opened in the browser.
Publish the template to the network and name it InitManager.xsn. Copy it later to your project folder.
Build the project, uncomment the following lines in the install.bat file, save and call install.bat.
::Note: Uncomment these lines if you've modified your deployment xml files or IP forms
stsadm -o deactivatefeature -filename U2U.ExpenseReport\feature.xml -url
http://wss.u2ucourse.com/sites/sergeworkflows/
stsadm -o uninstallfeature -filename U2U.ExpenseReport\feature.xml
http://wss.u2ucourse.com/sites/sergeworkflows/
Create a new workflow association and test the workflow; when you start it, the Initialization form should
show up :
We need to retrieve the manager user account (later on we’ll use a more specialized control, but in the
meantime keep the textbox .
One way is to generate a class based on the class schema that will be used to deserialize the xml
information stored in the InfoPath form : the .Net sdk tool xsd.exe provides such possibility.
Start the Visual Studio 2005 Command prompt (Visual Studio Tools menu):
Open the Infopath form in design mode, select the save as source File option in the file menu and save it
in a subfolder (e.g initformsources). The following files will be generated :
We need to run xsd.exe on the .xsd file :
A new file (myschema.cs) will be generated; add to it to your project and rename it to
TemporyManagerInitForm (make sure the class is still TemporyManager) as well as the class inside
.Let’s retrieve the form informations in the onWorkflowActivated1 event handler:
Lets modify our workflow design to use the provided manager or to retrieve it from the list.
If the manager is provided in the form, we will use it, otherwise we will retrieve it from the list.
Drag & drop and IfElse activity, move the retrieveManager activity to the left branch and rename it as
follows:
Set the ifUsualManager condition as a Code Condtion:
Open the InitManager Infopath form in design mode; go to the design task and click on Controls;Select
Add or Remove Custom Controls; click on Add, select ActiveX Control; click on next and select
ContactSelector:
Click next and select Don’t include a .cab file; For Binding Property select Value and click Next; from the
Field or group type box choose Field or group (any data type) and click Finish:
2°Create the data structure for the Contact Selector Control
The Contact Selector control needs to have a specific data structure to work
Spelling and capitalization must be exactly the same, starting with the “Person” group!
· Add the following 3 text fields to the Person group: DisplayName, AccountId and AccountType :
Drag and drop the TemporyManager group to the form and design the form like this:
Save the form as sources, publish, copy the published template to the project folder and re-generate the
class with xsd.exe as we previously did.
The code to retrieve the manager in onWorkflowActivated1 event handler should look like this :
if (tempManager.gpContactSelector.Length > 0)
this.Manager = tempManager.gpContactSelector[0].AccountId;
Rebuild the solution , call install.bat and test the workflow with and without a temporary manager.
The association form is very similar to the Initialization form: it will show up when the workflow
association is being made.The firm template should be inserted in the Association_FormURN child
element of the <MetaData> element (in workflow.xml)
Scenario
The manager needs all the necessary information to approve or reject the expense report; therefore we
need to display the expense report data into the task form.
Hands-on
The content of these extended properties will be sent to the Infopath Task form.
Create the following .xml file and name it ItemMetadata.xml (Spelling and capitalization must be exactly
the same here: both for the file name and for the extended properties name, each must be prefixed with
ows_):
Click on the ApproveRejectTask activity and add 3 new extended properties to the
ApproveRejectTaskproperties:
Add a new Data Connection to link the infopath form to this xml file:
Click on Next:
Click on Next and select the .xml file:
Click on Next and select the first option :
Keep the default options and click on Finish.
Now we are going to bind the form field to the new Data Connection fields:
Click on the insert field or group button and switch the Data source to ItemMetadata (secondary):
Select ows_submitter :
Click Ok.
Save and publish the form; make sur the published template is in your project folder.
Rebuild the solution, call install.bat and test the workflow by following the next scenario:
Start the workflow: normally Serge’s manager is Bill; if Bill is on vacation,serge will submit the expense
report to Jon: