Step by Step Tutorial. Creating Workflows for Windows Sharepoint Services and MOSS2007 http://sergeluca.spaces.live.com/blog/cns!E8A06D5F2F585013!859.entry
Step 1: Creating and testing the project Download the code 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. Introducing Microsoft Windows Workflow Foundation: An Early Look. 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.
2. Submitting an expense Report: the scenario
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:
• • • • •
Generate a unique identifier (a guid) for this expense report Check if the report must be submitted to approval : if the amount is less than 1000, the expense report will be automatically approved and the process is completed (status will be “autoapproved”) Find the user’s manager Create tasks for the manager to approve or reject the expense report Update the expense report status (“approved or “rejected”).
Before starting, make sure you have a team site with the following list: A list named “Expense Reports” with following columns:
A list named “Managers” with the following columns:
(very important , make sure the Manager and Manager Of columns are of type Person or Group with the Show Field option as account )
Fill up the Managers list with the following values:
3.2 Creating the workflow
Before starting Visual Studio 2005, make sure the followings components have been installed:
•
Visual Studio 2005 Extensions for Windows Workflow Foundation. Visual Studio 2005 Extensions for Windows Sharepoint services v.3 It’s time to create our first workflow: start Visual Studio 2005 and create a new project :
•
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:
Double click on ExpenseReportWorkflow.cs to visualize the workflow in the Workflow Designer
Take a look at the referenced assemblies in Solution Explorer:
3.2 Setting up the Sharepoint activities The Sharepoint team provides a set of activities and most of them are compiled in the microsoft.sharepoint.WorkflowActions.dll that can be found in the C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\ISAPI folder. 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:
•
Sharepoint will communicate with the workflow by sending events • The workflow will invoke Sharepoint by calling a method on a dedicated interface that will redirect the call to the Sharepoint api. The interfaces we will have to deal with are defined in the Microsoft.Sharepoint.Workflow namespace of the Microsoft.Sharepoint assembly and are decorated with ExtenalDataExchangeAttribue (see the Windows Services sdk):
• • •
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.
3.3 Finishing, deploying and testing our “hello world” workflow
The first activity in a Sharepoint workflow must be the Microsoft.SharePoint.WorkflowActions.OnWorkflowActivated activity. Drag and drop an OnWorkflowActivated activity into the designer:
Set its CorrelationToken property as follows:
CorrelationToken are extensively used is in Workflow-Sharepoint programming and are an interesting way to logically group activities together; more details about this later… 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 :
Remove any other tags. 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. Start the install.bat and check any potential error message. 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. Go to the ExpenseReports list add a new Expense Report:
Start the workflow menu associated with the item:
Click on the ApproveReject button. 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 Click here to get to the previous article. 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. Retrieving the expense report details This is straightforward in Sharepoint : we just need to bind the OnWorkflowActivated activity with a workflow member: Select the OnWorkflowActivated1 activity, Select its workflowProperties in the property page and Click on the ellipsis button, Select the tab Bind to a new member 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. Add 4 new public data members to the ExpenseReportWorkflow class :
public sealed partial class ExpenseReportWorkflow: SequentialWorkflowActivity { public float Amount; public string Description; public string SubmittedBy; public string Status; Double click on the Invoked event of the onWorkflowActivated1 activity :
A new event handler, onWorkflowActivated1_Invoked, will be generated: we will retrieve the expense report values at this level.
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();
Debugging the workflow 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. Type the following condition:
You’ll notice that a .rules file has been generated this file contains expressions describing or rules / conditions. The language for these expressions is CodeDom. We’ll get back to this
Drag and drop 2 code activities and rename them as follows:
Double click on the Authorize activity and associate it with the following code:
private void authorize_ExecuteCode(object sender, EventArgs e) { WorkflowProperties.Item["Status"] = "Automatically Approved"; WorkflowProperties.Item.Update(); }
Double click on the requestManagerApproval activity and associate it with the following code :
private void requestManagerApproval_ExecuteCode(object sender, EventArgs e) { WorkflowProperties.Item["Status"] = "Automatically Approved"; WorkflowProperties.Item.Update();
} Start install.bat and test the workflow on your list item:
Test the workflow with amounts greater than 1000.
Step 3: Extending the workflow: finding the manager and creating a custom activity Download the code The Scenario : Click here to get to the previous article.
If the expense report requests manager approval, we need to find the current user’s manager and assign him a task. In this step, we will focus on finding the manager. We will code this in a custom code activity and we will refactor and encapsulate our code in a custom activity. Make sure the Sharepoint object model namespace has been specified in ExpensereportWorkflow.cs:
using Microsoft.SharePoint;
Let’s create a general function that Retrieves a manager from a list :
private string FindManager( string managerListTitle, string managerColumnName, string manageeColumnName, string managee, SPWeb webSite) { SPList managerList = webSite.Lists[managerListTitle]; string manager = string.Empty; foreach (SPListItem managerItem in managerList.Items) { if (managerItem[managerColumnName] == null) break; if (managerItem[manageeColumnName] == null) break; //workflowProperties.Originator is the userId if (managerItem[manageeColumnName].ToString().Contains(managee)) { manager = managerItem[managerColumnName].ToString(); return manager; } } return manager; }
We can invoke this function from our requetsManagerApproval event handler: SPWorkflowActivationProperties.Originator is the user id of the workflow caller. SPWorkflowActivationProperties.Web is the web site associated with the workflow instance.
private void requestManagerApproval_ExecuteCode(object sender, EventArgs e) { string manager = this.FindManager( "Managers", "Manager", "Manager Of", WorkflowProperties.Originator, WorkflowProperties.Web); WorkflowProperties.Item["Status"] = "Must be approved by " + manager; WorkflowProperties.Item.Update(); }
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:
Reference Microsoft.Sharepoint.dll in order to use the Sharepoint Object Model :
Delete the file Activity1.cs and add a new Activity component (Select Add new Item-Activity). Name the file RetriveManager.cs:
Go the activity code and derive RetrieveManager from the Activity class :
public partial class RetrieveManager: Activity { public RetrieveManager() { InitializeComponent(); } Declare: 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 :
Define a dependency property for the WebSite :
Replace MyProperty with WebSite:
Replace the type with SPWeb:
Create another 2 other Dependency properties : Managee and Manager; keep string as the type. Override Execute Method of the Activity:
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) { 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; } }
Sign and rebuild your assembly. If you display the ExpenseReportWorkflow in the Workflow Designer, the new Activity should show up in the toolbox :
We can drag and drop it just before the RequestManagerApproval activity:
Set the properties known at design time: (pay attention to the case sensitivity):
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 : Create a new public field in the workflow : Manager
public sealed partial class ExpenseReportWorkflow: SequentialWorkflowActivity { public string Manager;
Bind the activity Manager property to rthe workflow Manager property:
In the workflow, update the requestManagerApprovalexecuteCode function to display the Manager:
private void requestManagerApproval_ExecuteCode(object sender, EventArgs e) { WorkflowProperties.Item["Status"] = "Must be approved by " + this.Manager; WorkflowProperties.Item.Update(); } 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.
Step 4: Creating the tasks •
Download the code Scenario 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:
private void ApproveRejectTask_MethodInvoking(object sender, EventArgs e) { this.ApproveRejectTaskId = Guid.NewGuid(); this.ApproveRejectTaskProperties.ApproveRejecTaskProperties.Title = "Approve or Reject this expense report"; this.ApproveRejectTaskProperties.AssignedTo = Manager; }
Remove the requestManagerApproval activity. OnTaskChange activity Add a new OnTaskChange activity and rename it WaitForApprovalRejection:
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). Set the correlation token to ApproveRejectToken.
This is not enough, the event doesn’t carry the taskId you still need to associate this activity with the taskId: Bind the the TaskId property to an existing member: ApproveRejectTaskId. 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. Set the correlation token to ApproveRejectToken. Bind the TaskId property to an existing member: ApproveRejectTaskId. 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:
If we click on the status, we’ll get more details:
Indeed, a task has been assigned to Jim; if you click on the task Title, you’ll be redirected to the task list:
If you click on the task you’ll get more details (including the workflow association name: AcceptReject):
If Jim change the task and set it to completed:
The workflow will complete. There are many situations where the out of the box behavior is not enough, for instance if you want to display more detailed informations about the expense report, or more specific replies (approved or rejected). Then we need to create either Infopath forms (only on MOSS) or aspx form (on Wss v3 or on MOSS).
Step 5: Creating and Using Infopath Tasks Forms Download the code Creating and linking the Infopath form (start with Step 4 solution) Let’s start Infopath 2007. Select Design a Form Template-Blank:
Check the option Enable browser-compatible features only. Design the following form with 2 buttons :
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. We are going to add 3 actions : setting the status field to Accepted or Rejected,
Submitting the data Closing the form 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. With Windows Explorer, go to the form, right click on it:
Select Design and go to the File menu, select Properties:
Copy the ID to to the clipboard. Modify the manifest file (workflow.xml) in order to declare this form as the form to use to interact with the task: In the MetaData element, create a Task0_FormURN child element :
Several forms can be used (see part 6 of this tutorial) therefore the code in the workflow will refer to the form by specifying the number provided in the Task<X> element name; for instance here <X> is 0, so in the code we will refer to the form 0. 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:
ReceiverAssembly="Microsoft.Office.Workflow.Feature, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
ReceiverClass="Microsoft.Office.Workflow.Feature.WorkflowFeatureReceiver" Register the Infopath form in the feature.xml file:
Modify the install.bat file in order to copy the Infopath Form :
In the same file, uncomment the lines following the Note : ::Note: Uncomment these lines if you've modified your deployment xml files or IP forms
In the workflow designer, double click on the ApproveRejectTask activity : in the ApproveRejectTask_methodInvoking, set the link between the Task and the TaskForm0 (which references our Infopath form):
private void ApproveRejectTask_MethodInvoking(object sender, EventArgs e) { … this.ApproveRejectTask.TaskProperties.TaskType = 0; } Now, let’s retrieve the information coming from the Infopath Form : is our expense report approved or rejected ? We can retrieve these value at the level of the workflow by using the AfterPropertries property of the OnTaskChange activity :
The type of AfterProperties is Microsoft.Sharepoint.Workflow.SPWorkflowTaskProperties. The data coming from the Infopath form controls will be stored in its ExtendedProperties property which is an Hashtable. Let’s bind AfterProperties to a new field (AfterApprovRejectProps) in the workflow class:
Double click on the WaitForApprovalRejection activity ,r etrieve the Infopath fields value from the HashTable (ExtendedProperties) and change the ListItem status :
private void WaitForApprovalRejection_Invoked(object sender, ExternalDataEventArgs e) { string acceptedRejectedStatus = this.AfterApprovRejectProps. ExtendedProperties["status"].ToString(); this.WorkflowProperties.Item["Status"] = acceptedRejectedStatus; this.WorkflowProperties.Item.Update(); }
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.
Step 6: Using Several Task Forms Download the code
Scenario: 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 Make a copy of the existing Infopath form and rename it Reject.xsn. Rename the original form to Accept.xsn. Design reject.xsn , change the header and remove the accept button:
Go to the File-properties menu and change the form id:
Publish the form as we did in the previous step, but here rename the Form template to Reject and the form template file to Reject.xsn :
Remember to keep the textbox empty in the next step… Design Approve.xsn and remove the Reject button:
Publish this form as we did for the Reject form , but the form template should be Approve and the file Approve.xsn. Copy both published files to your project folder.
Drag and drop a ParallelActivity after the requestManagerApproval activity and rename the activities as follows:
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 WaitForApprovalRejection to onTaskApprovalChanged. Rename onTaskChanged1 to onTaskRejectionChanged. Rename ApproveRejectTaskComplete to approveTaskComplete. Rename completeTask1 to rejecteTaskComplete. Rename the ListenActivity to WaitForManager 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. Set the Invoked property of onTaskRejectionChanged to a new event handler: WaitForRejectionTask_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. Changing the taskId 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.
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
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 . Changing the AfterProperties Bind the Afterproperties property of onTaskApprovalChanged and onTaskRejectionChangedand 2 new TaskProperties field members : AfterApprovProps and AfterRejectProps. Set the task Guid, task form and task properties Code the invoking methods as follows: private void RejectTask_MethodInvoking(object sender, EventArgs e) { this.RejectTaskId = Guid.NewGuid(); this.RejectTaskProperties.Title = "Reject this expense report"; this.RejectTaskProperties.AssignedTo = Manager; this.rejectTask.TaskProperties.TaskType = 1; } private void ApproveTask_MethodInvoking(object sender, EventArgs e) { this.ApproveTaskId = Guid.NewGuid(); this.ApprovedTaskProperties.Title = "Approve this expense report"; this.ApprovedTaskProperties.AssignedTo = Manager; this.approveTask.TaskProperties.TaskType = 0; } 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: private void onTaskApprovalChanged_Invoked (object sender, ExternalDataEventArgs e) { string acceptedRejectedStatus = this.AfterApprovProps. ExtendedProperties["status"].ToString(); this.WorkflowProperties.Item["Status"] = acceptedRejectedStatus; this.WorkflowProperties.Item.Update(); } private void onTaskRejectionChanged_Invoked (object sender, ExternalDataEventArgs e) { string acceptedRejectedStatus = this. AfterRejectProps. ExtendedProperties["status"].ToString(); this.WorkflowProperties.Item["Status"] = acceptedRejectedStatus; this.WorkflowProperties.Item.Update(); } 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.
Step 7: Using association and initialization forms Download the code 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 We’ll start from step5 solution. Let’s design a new blank Infopath form template:
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. Add a button, name it Submit and link it to the following actions:
Submit data using a data connection (select the Submit data connection) Close the form 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 : At the VStudio 2005 console, type :
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: XmlSerializer serializer = new XmlSerializer(typeof(TemporyManager)); XmlTextReader reader = new XmlTextReader(new System.IO.StringReader(WorkflowProperties.InitiationData)); TemporyManager tempManager = (TemporyManager)serializer.Deserialize(reader); this.Manager = tempManager.Account;
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:
Here is the Conditional function code :
private void ifManagerNotProvided(object sender, ConditionalEventArgs e) { e.Result = this.Manager.Trim() == string.Empty;
}
Build your solution, call install.bat and test the workflow. Using the Contact selector control to select the Temporary Manager 1°Install the Contact Selector control: 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 a non-Repeating Group named: gpContactSelector
·
Add a Repeating Group named: Person
·
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 :
XmlSerializer serializer = new XmlSerializer(typeof(TemporyManager)); XmlTextReader reader = new XmlTextReader(new System.IO.StringReader(WorkflowProperties.InitiationData));
TemporyManager tempManager = (TemporyManager)serializer.Deserialize(reader); 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) < Association_FormURN> Association_FormURN>
Step 8: Bringing data from the workflow to the task form Download the code 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 Start from step 7 solution. Open the Infopath task form ( ApproveReject.xsn) in design mode . Update the form like this:
Rename the controls in order to have the following Data source:
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: In the task form, click on the submitter field :
In the data panel, click on the formula (fx) button:
Click on the insert field or group button and switch the Data source to ItemMetadata (secondary):
Select ows_submitter :
Click Ok. Repeat this for the amount and the description fields. 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: Add a new user account : Jon; Jon will be Bill’s manager.
Log in as Serge (Serge’s manager is Bill, not Jon) and submit the following expense report:
Start the workflow: normally Serge’s manager is Bill; if Bill is on vacation,serge will submit the expense report to Jon:
A task will be assigned to Jon:
If Jon clicks on the task hyperlink: