September 2009
September 2009
Page 2 of 109
Contents
SUMMARY.........................................................................................................................3
DESCRIPTION OF THE WORKFLOW.............................................................................3
SUMMARY SNAPSHOTS OF OUR WORKFLOW...........................................................4
OVERVIEW OF THIS TUTORIAL...................................................................................12
STEPS INVOLVED IN THE PROCESS..........................................................................12
SOFTWARE REQUIRED TO DUPLICATE THIS PROJECT..........................................13
STEP 1 – CREATE A NEW LIBRARY IN SHAREPOINT...............................................13
STEP 2 – CREATE A TEMPLATE FOR THE BIRTHDAY CARD..................................17
STEP 3 – CREATE THE WORD DOCUMENT FOR THE BIRTHDAY CARD FROM
WITHIN THE LIBRARY...................................................................................................20
STEP 4 – CREATE OUR INFOPATH 2007 FORM TO ACCEPT INPUT FROM THE
USERS.............................................................................................................................35
STEP 5 – CODE OUR WORKFLOW IN VISUAL STUDIO.............................................70
STEP 6 – CONFIGURE WORKFLOW.XML AND FEATURE.XML IN VISUAL STUDIO
.........................................................................................................................................92
CONCLUSION.................................................................................................................95
Page 3 of 109
SUMMARY
This tutorial will demonstrate how to create a SharePoint sequential workflow that will
reveal many useful techniques that can be applied in the field. The purpose of this
workflow is to send a Birthday Card (as a Word document) via Email to a SharePoint
user with some of the text of the Birthday Card filled out dynamically in C# code during
the workflow. The dynamically populated fields will get their input from an InfoPath
form presented to the person who is assigned the task of signing the card. This is
similar to what might happen in an office today when your supervisor wants a person in
his/her group to send a birthday card to a particular birthday boy or girl. In our situation,
the supervisor creates the card from a SharePoint library as a Word document (as a
content type) which has a workflow attached to it. The difference here is that the
process is under the management of the workflow which we will code in Visual Studio
2008.
Note: This is a long tutorial with a vast number of steps required to complete it. The
reader should be alert to how complex the process is to create a very simple sequential
workflow that does some fancy work behind the scenes using InfoPath forms.
Let’s take a moment to better describe what we want. A birthday card will be created as
a content type for a SharePoint library that we will create. We will call the library Happy
Birthday Card Library. A birthday card will be a Word document, with a picture of a
birthday cake, and several pre-defined fields as Word properties which are linked to
column types in the library. Some of these fields will be filled out by the initiator of the
birthday card (the supervisor most likely) which will include the name of the recipient
(E.g. “Jane Smith”), an opening greeting (E.g. “Best wishes on your Birthday!”), the
email address of the recipient, and the login name of the person assigned the task of
signing the card (E.g. [email protected]). There will then be two additional
fields which need to be populated by the signer of the card. These two fields are filled
out by using an InfoPath form. These fields are from (E.g. “John”) and the message
the signer would like to include (E.g. “Hope you like the cake!”). When the signer
completes the workflow task, the workflow will finish populating the Word document
Birthday Card with the two fields obtained from the InfoPath form, and then email the
updated Birthday Card to the recipient as an email attachment. The workflow will then
mark the task as completed. The other thing we will do is disable the submit button on
the InfoPath form when the workflow task is completed. If we did not do this, then the
user could re-submit the form again and again from the Tasks page. There will have to
be a feedback mechanism for the InfoPath form to make it aware that the task was
completed. We disable the submit button in this example.
Page 4 of 109
Page 5 of 109
For reference, the snapshots below summarize the procedure of sending the birthday
card.
1. Administrator creates a Birthday Card (Figure 1)
Figure 1
Note that Mary Smith was assigned the task of signing the card (E.g. Assigned To
Login Name = MY_NETBIOS_NAME\marysmith in the property field Figure 2)
3. Exit Word (Figure 3)
Page 7 of 109
Figure 4
6. We now sign in as Mary Smith, signer of the card. The task is present on
her task list. (Figure 6)
Page 9 of 109
7. Mary clicks “Please sign this birthday card” and gets the InfoPath form
which she fills out 2 fields. (Figure 7)
8. Mary clicks the submit button and the task completes. (Figure 8)
9. Recipient of the card gets an email with the filled out Birthday Card. (figure
9 & 10)
Figure 9
Figure 10
11. Notice that we disable the Submit button in the InfoPath form if Mary tries
to fill it out a 2nd time. (Figure 12).
Figure 12 – Submit button is disabled via feedback from the Workflow on subsequent
tries
So, that’s the flow. It’s very simple from the user’s perspective. However, to get all this
working requires a considerable amount of knowledge and work. Let’s get started.
Page 13 of 109
This tutorial has a huge number steps that need to be performed to set up this workflow.
There is very little C# code in the Workflow, and most of the work is done outside of the
workflow programming in preparation. Also, this tutorial is quite long since there are so
many pieces that need to be discussed to get all of this to work together. Also, there
are some issues that would need to be addressed in a production environment. One of
them is that we only have one Word document Birthday Card in our SharePoint library.
Our C# code references this Word document by its filename and is hard coded. We
would need to enhance the C# code to allow any filename to be used for a Birthday
Card. One way to do this is to include another property on the Birthday Card Word
document named Filename which the initiator of the card would fill out. Another issue
is that our InfoPath form takes over the page where a user would normally change the
% complete and status of the Workflow. That is why we update the completed status
in the Workflow rather than let the user do it manually. Students of SharePoint Minds
are encouraged to modify this Workflow to address those issues.
In any event, this tutorial has a wealth of good information regarding SharePoint
workflows that would be helpful.
NOTE: It should be noted that this author created this library under the Team Site tab of
his main portal SharePoint site. This means that the columns that we create (E.g. To,
From, Greeting, Assigned to Login Name, Email Address, Completed, and Message) in
our Happy Birthday Library are not global site columns, but local to the Team Site.
The software requirements for this project are significant. The software that you will
need is as per the following list:
1. Microsoft ASP.NET 3.5 Framework
2. Microsoft Windows Server 2003 installed on the Virtual Machine (E.g. the VM)
3. Ability of the virtual machine to connect to the internet using the Microsoft
Loopback adapter (see the document on this topic by this author on the
SharePointMinds.com site).
4. Microsoft SharePoint 2007
5. Microsoft SQL Server 2005 (required by SharePoint)
6. Microsoft Visual Studio 2008
7. Windows SharePoint Services 3.0 Tools: Visual Studio 2005 Extensions, Version
1.2
8. InfoPath Forms 2007
9. Visual Studio Tools for Applications 2007
10. Microsoft Word 2007
On your SharePoint site (in this case, the authors Team Site), click Site Actions >
Create as shown in Figure 13.
Page 15 of 109
Figure 13
Page 16 of 109
Figure 14
Page 17 of 109
Fill out the form for the new library and Click Create. Refer to Figure 15.
Figure 15
Figure 16
In this step, we are going to create several new columns. These columns will be
available in the Happy Birthday Card Word document that we create and will become
Word document objects. SharePoint will take care of the linking for us. We will be able
to access the contents of the Word objects in the C# code behind. That means that we
can read the fields, but it also means that we can write to the fields in the C# code
behind in the Workflow. Therefore, the Word document serves as an input form where
we can enter values in the fields. It is intended that only 4 of the fields are filled out
when the administrator creates a new Birthday Card (E.g. To, Greeting, Assigned To
Login Name, Email Address). The remaining fields will be filled out in C# code using
data from the InfoPath form.
In the Happy Birthday Card Library, click Settings > Document Library Settings as
shown in Figure 17.
Figure 17
Now we want to create some new columns for our Birthday Card. We are going to have
to repeat the process for each of our columns. Column names in red are required fields
to be filled out by the creator of the card. Our columns are:
1. To
2. Greeting
3. From
4. Message
5. Email Address
Page 19 of 109
The first four columns will be filled out by the person who starts the Birthday Card
process from the library. That is, when he/she creates new instance of the Birthday
Card by clicking the New > Happy Birthday Card menu selection in the library.
To: will be the name of the person who will receive the Birthday Card. Example: To:
Jane Smith
Initial Greeting: The second line will be the initial greeting which is the basic text of the
card to be displayed. Example: Happy Birthday Jane!
Assigned To Login Name: The ID of the person assigned the task of signing the card
and providing a message. Example: yourdomain\johnsmith
The remaining columns will be filled out dynamically by the workflow that we will code.
The content of the remaining columns will be obtained from information that is submitted
when the signer completes their workflow task by filling out an InfoPath 2007 form
during the workflow process.
So now let’s add come columns. We will only show the process for the first column.
The rest of the columns will follow the same procedure.
Figure 18
The resulting page is shown in Figure 19. Fill out the form as shown in Figure 19.
NOTE: All the fields will be single line of text for all our columns. Also, four of the fields
will be required fields, so check the radio button to make it a required field. Recall that
we denoted the required columns in red above.
Figure 19
Repeat this process for our remaining columns. The final result showing all columns
created should appear as shown in Figure 20 (except for title which was there already).
Page 21 of 109
Return to the Birthday Card Library in SharePoint. From the New > New Document
menu selection, create a new Word document (which will be aware of our columns
template that we just created). Refer to Figure 21.
Page 22 of 109
Figure 21
An instance of a blank Word document will appear and will show our columns template
as shown in Figure 22.
Figure 22 – Note the text boxes, 4 with red asterisks denoting required fields
Next, convert the Word document as shown in Figure 23 and Figure 24.
NOTE: You must convert the document or you will not be able to create the objects that
are linked to the 7 site columns that we created.
From the main menu, click Convert. A dialog will appear as in Figure 24. Click OK.
Page 23 of 109
Figure 23
Figure 24
Now we must save the document to a directory on disk. Do not save the document in
the SharePoint library which is the default. Save it to a directory of your choice on
disk. To save the document, this author navigated to his My Documents directory as
shown in Figure 25.
Page 24 of 109
Figure 25
Next, we added a Happy Birthday text at the top and added a picture as shown in
Figure 26. Once you have done this, save the document (again).
Page 25 of 109
Now we need to add some Word Objects to our document in the body just below the
picture of the cake. We will perform the following procedure for all of our columns in
turn.
Place the cursor in Word where you would like to create the To object (just below the
cake for this one). Then, Click the Insert tab, then Quick Parts > Document
Property. Refer to Figure 27.
Page 26 of 109
Figure 27 – An object corresponding to the menu item To will be placed at the cursor.
On the resulting dropdown, scroll down to the To listing and click it. This will cause the
To object to be inserted at the cursor in the Word document as shown in figure 28.
Page 27 of 109
NOTE: These objects in Word will be available in our C# code behind in the workflow.
We can either read from them, or write to them in code.
Perform these steps again in turn, for our remaining objects. When finished, the Word
document should appear as in Figure 29.
Page 28 of 109
Figure 29 – 4 of 7 of our template columns are now objects in the body of the Word
document. We never show the other 3 properties on the card.
Our Word document is now finished. Click save (remember not to save in the
SharePoint library, save it to the same directory you have been using), and then exit
Word by clicking Exit Word as shown in Figure 30.
Page 29 of 109
We now need to add our Happy Birthday Word document to our library. To do that,
navigate to our Happy Birthday Library, and click Actions > Open in Windows
Explorer as shown in Figure 32.
Page 30 of 109
Figure 32
When explorer opens, click on the Forms icon, and then you should see the various
forms as shown in Figure 33. We need to copy and paste our
Happy_Birthday_Card.docx file to this directory. To do that, navigate to the location
where you saved the Word document, copy it, and navigate back to this directory and
paste it. The result should be as shown in Figure 34.
Next, click on Settings > Document Library Settings as shown in Figure 35.
Figure 35
We now need to add the content type for our Happy Birthday Card Word document to
the library. As shown in Figure 36, click on Add from existing content types.
Note: if your page does not show the Content Types section, refer to Appendix A to get
it to display.
Page 32 of 109
Figure 36
Now, using Figure 37, find the content type Birthday Card in the left hand list box, and
add it to the right hand list box.
Figure 37
Page 33 of 109
Note: The content type of Birthday Card should appear in the left hand list box because
we placed our Word document in the Forms directory of the library. Your name may
differ depending on what you saved the Happy Birthday Card Word document filename
to.
We should now see a new content type in our library as shown in Figure 38.
Figure 38
NOTE: this author recommends that you change the order of the content types shown in
Figure 38. Place the Birthday Card content type first, and move Document below it. You
can reverse the order by clicking the link just below the red circle in Figure 38 (its cut off
in the figure) called Change new button order and default content type.
Next, we need to specify the template to use for the Birthday Card content type. As
shown in Figure 39, click on Advanced Settings.
Page 34 of 109
Figure 39
On the resulting page, add the URL of our Happy_Birthday_Card.docx file as shown
in Figure 40. Remember we just pasted the document in the forms directory. Click OK.
Figure 40
Now, let’s check if our blank Birthday Card loads. As shown in Figure 41, click on New
> Birthday Card
Page 35 of 109
Figure 41
Our Birthday Card Word document should open up as shown in Figure 42. If it does, all
is well and we can move on to the next step. Close the Word document without saving
it.
Figure 42
Page 36 of 109
QUESTION: Should our Happy Birthday Card been installed as part of the deployment
process where a deployment package would be executed on each server in the farm?
ANSWER: Not necessarily. Remember that content types are stored in the content
data base. It will therefore be available to all servers in the farm. So, installing it as we
did here only need be done once.
The purpose of this InfoPath form is to collect the name and birthday greeting from the
person assigned the task of signing the card in the workflow. Also, we want it to be
presented in a web page rather than in the InfoPath client (which is the default). We will
have to specifically configure the InfoPath form to open in a web browser. We also will
provide some validation. If either of the 2 fields are blank, we need to prevent the user
from submitting the form. Also, we will need to provide the means to disable the Submit
button if the workflow has been completed.
4.1 Open the InfoPath application and create a new blank form project
We now need to open the InfoPath application and create a new blank template as
shown in Figure 43.
Page 37 of 109
Figure 43
On the resulting screen under Design Tasks, click on Layout as shown in Figure44.
Figure 44
Select the Table with Title layout. You should then see a new layout as shown in
Figure 45.
Page 38 of 109
Figure 45
Change the title to Enter Birthday Card Message as shown in Figure 46.
Figure 46
This author changed the color scheme by clicking the navigation items as shown in
Figure 47.
Page 39 of 109
Figure 47
Next, we add 2 rows beneath the title, and then we split those rows into 2 columns as
shown in Figure 31. This is done by right clicking on the table, and selecting the
appropriate menu items to add rows and split into 2 columns. Refer to Figures 48 & 49.
Figure 48
Page 40 of 109
Figure 49
From the right hand Task Pane as shown in Figure 50, click the drop down arrow and
select Controls from the menu. We are now going to add a text box on each of our
three rows in the table.
Figure 50
Drag a text box onto each row of our table as shown in Figure 51, and add the text in
the left hand columns as shown.
Page 41 of 109
Figure 51
4.5 – Modify the names of the default DataSource fields assigned to our text
boxes
NOTE: When you create a form template, Microsoft Office InfoPath automatically
creates a default data source for you. All InfoPath form templates contain a single,
default data source. You add fields and groups to the main data source by using the
Data Source task pane or by dragging a control from the Controls task pane onto a
view in the form template. When you drag a control onto a view, InfoPath adds fields
and groups to the default data source according to the type of control you are adding.
For example, if you drag a text box control onto your form template, InfoPath adds a
field to the main data source. In our case, we see three fields added to the default data
source since we dragged three text boxes onto our form.
We now want to rename our fields to have them correspond to what our text boxes are
intended to contain. Right click on the first text box, and select Text Box Properties as
shown in Figure 53.
Page 42 of 109
Figure 53
Change the name of the field to From, check the checkbox Cannot be blank and click
OK. See Figure 54. Do the same for the 2nd text box, but enter the field name Message
and click OK. Do the same for the 3rd text box but enter the name Completed, but do
not check the checkbox Cannot be blank. Our form should look like Figure 55.
Figure 54
Also, from the Display tab, check the checkbox Read Only for the Completed text box.
We do not want the user to be able to type in the Completed text box. We only want
him/her to type in the From and Message text boxes.
Page 43 of 109
Figure 55
We will need a button for the user to click after he/she has filled out the form. We add
that now. From the Task Pane on the right, click to get the drop down, and then select
Controls from the resulting menu. Drag a button onto our form as shown in Figure 56.
Figure 56
Click on the Preview button on the menu bar. The preview should appear as in Figure
57.
Page 44 of 109
Right click on the button, and click Button Properties from the menu. Enter the name
Submit as shown in Figure 58. Click OK.
Figure 58
Page 45 of 109
4.9 – Add a rule to the button to disable it if the text boxes are blank
As in typical .NET programming, we need some validation when the button is clicked on
the form. We need to make sure the user entered something in the text boxes. How
about if we disable the button until the text boxes contain something? Right click on the
button and click on Conditional Formatting as in Figure 59.
Figure 59
On the resulting dialog box, Click Add. This causes a 2nd dialog to appear as in Figure
60. Add the condition as shown in the Figure. Also, note that the checkbox Disable
this control is checked. This means that if either text box is blank, our Submit button
will be disabled.
Page 46 of 109
We now have to add a rule that says when the Completed text box has a value of “1”
(denoting the condition true), then we must disable the button in that case as well. So,
add a second rule as we did for the first, except this time set the Conditional Format
to that shown in Figure 61.
Figure 61
Page 47 of 109
We can now test the validation by using the Preview button on the menu bar. In
preview mode, add some text to both text boxes. The button should go from the
disabled state to the enabled state as shown in Figure 61.
4.10 – Add a rule to send data to the server when the button is clicked.
We now need to configure the button so that it sends data to the server when the button
is clicked. To do that, right click on the button as we have done before, and we get the
dialog box as shown in Figure 62.
Page 48 of 109
Figure 62
After you click the Rules button as shown in Figure 62, a new dialog will appear. On
that dialog click Add to add a new rule. The resulting dialog will appear as in Figure 63.
Page 49 of 109
Figure 63
Click on the Set Condition button and a new dialog will appear. Here we are going to
configure the button to send data to the server when the From and Message text
boxes are populated and the button is clicked. Refer to Figure 64.
Page 50 of 109
Click OK to get back to the previous dialog. On that dialog click on Add Action. In the
resulting dialog, click on Submit using a Data Connection. Refer to Figure 65.
Now we have to configure the connection. Click the Add button and a new Wizard will
appear as in Figure 66.
Select the defaults as shown in Figure 66 and click Next. The wizard will move to the
next screen as shown in Figure 67.
Page 52 of 109
Figure 67
Accept the defaults as shown in the Figure and click Finish. When the wizard
disappears, we need to add another action to the button. Bring up the dialog as we
have done before as shown in figure 69.
Page 54 of 109
Click on Add Action, and in the resulting dialog, click on Close the Form from the
dropdown. The results should appear as in Figure 70.
Page 55 of 109
Figure 70
Keep clicking OK to close out all the dialog boxes. So, what we have configured is that
when the text boxes have text, enable the button. When the button is clicked, submit the
data to the server and close the form.
4.11 – Configure the form to receive data from the workflow on load.
When the form is submitted, the signer of the card enters text into the From and
Message fields that are sent to the server. Our C# code behind in the workflow will
read the data submitted and will cause the task to be changed to the completed state
when this happens.
However, the signer of the card could go back to the task list and try to submit the form
again. When he/she clicks the Please sign this birthday card link on subsequent
tries on the SharePoint Tasks page, the form will load again, but we want to populate
the form on these subsequent loads. This is the case where we want to disable the
Submit button on the form, and additionally, we would like to populate the form with the
values that were originally submitted by the signer. We don’t want to simply put up a
blank form again. Remember, the form was already successfully submitted and the task
was placed in the completed state.
So, to populate the form on subsequent loads, and also disable the Submit button, we
need to configure a secondary data connection. This data connection will be a receive
data connection.
Page 56 of 109
NOTE: The creation of the ItemMetaData.xml file described in the following section was
created by this author in the Workflow project in Visual Studio 2008, and the xml file is
part of the solution files. It is recommended that you create a Visual Studio Workflow
blank project, add an Xml file named ItemMetaData.xml and refer to that file in step 2
below. This tutorial will eventually get to create the workflow in VS2008. Refer to Figure
71. You don’t have to include the file in the project as this author has. You can simply
save it to the directory of your choice if you want to.
1. We will want to update our task form from the workflow. To send data to the form
fields from the workflow, we need to create a receive data connection. For this,
we will use a very simple text (XML) file.
2. As noted above, create an Xml file in Visual Studio 2008. Name it:
ItemMetadata.xml. The name is case-sensitive and any other name will not
work (Watch the case of the ‘d’ character, it’s lowercase).
3. Inside this file place the following line of text: <z:row
xmlns:z=”#RowsetSchema” ows_From=”” ows_Message=””
ows_Completed=”” />.
4. Save and exit the file. If, in future you want to pass other data back to a task
form, just create a similar file and reference the fields in your form by adding
ows_ before the field name. You can have multiple fields, separated by a space.
5. Back in InfoPath, select Data Connections from the Tools menu.
6. Click the Add button and create a new data connection to receive data. A wizard
will start as shown in Figure 72.
7. Select the radio button Receive Data as shown in Figure 72. Click Next.
8. Select XML document as our data source and click Next. See Figure 73.
9. In the next wizard screen, browse to ItemMetadata.xml file you just created and
select it. Note that it’s the file in our Visual Studio 2008 project. See figure 74 and
75. Click Next.
Page 57 of 109
10. IMPORTANT - As shown in Figure 76, select the radio button Include data as a
resource file in the form or form template part . Click Next.
11. Leave the remaining settings at their default value and click Next until Finished.
12. Back in the form, double-click on the From field.
13. We’re going to set the default value to that passed into the data connection via
the workflow. Click the Fx button next to the default value field as shown in
Figure 78.
14. Click the Insert field or group button as shown in figure 79.
15. From the Data Source drop-down, select ItemMetadata as shown in Figure
80.
16. Select the ows_from field and click OK . Refer to Figure 80 again.
17. The field should appear as shown in Figure 81. Click OK until all dialogs close.
18. Repeat this process for the remaining two fields (E.g. Message and Completed)
Figure 72
Page 58 of 109
Figure 73
Page 59 of 109
Figure 74
Figure 75
Page 60 of 109
Figure 76
Page 61 of 109
Figure 77
Page 62 of 109
Figure 78
Page 63 of 109
Figure 79
Page 64 of 109
Figure 80
Page 65 of 109
Figure 81
We want our form to be displayed in a browser, not in the InfoPath client. That means
that the form will appear in a web page in SharePoint. To do this, we have to make the
form browser compatible.
On the top menu bar, click Tools > Form Options. A dialog box will appear as shown
in Figure 82. On the left hand side, scroll down to Compatibility and click the check
box Design a form template that can be opened in a browser or InfoPath . Then
click OK.
Page 66 of 109
Figure 82
The form will be opened in a web page, and SharePoint will require that the form be
trusted. We need to configure the Form so that SharePoint will allow it to be opened.
On the top menu bar in InfoPath, click Tools > Form Options. A dialog box will
appear as shown in Figure 83. On the left hand side, scroll down to Security and
Trust and click the radio button Full Trust. Then, check the checkbox Sign this form
certificate. You will need to click the button Create Certificate during the process.
This certificate will only be a temporary certificate for our development purposes. In
practice, a real certificate may be required. When finished, the dialog should look like
Figure 83.
Page 67 of 109
Note: This author found that SharePoint complained that the form was not trusted and
would not allow it to be opened. Making the changes to Full Trust with certificate fixed
the problem.
Figure 83
We can now save our form by clicking File > Save from the main menu. We are going
to save our form to the same location that we saved our Word document. This author
named the file HappyBirthdayForm_one.xsn.
Note: Do not end the name of the file in a number. Problems can arise since
SharePoint can add numbers to the file name under certain conditions.
We now need to publish our form. Click on File > Publish. The publishing wizard will
appear. On the first Wizard dialog, we select the radio button To a Network Location.
We then browse to the same location we stored our Word document. This author
named this file HappyBirthdayForm_one_Published.xsn. Click out the wizard by taking
the defaults that are presented.
Note: The published .xsn file we create will be imported info Visual Studio later in this
document. It will then be deployed as a feature where is will show up in Central
Administration under Manage Form Templates automatically.
We will need to enter the form ID in the workflow.xml file in Visual Studio since Visual
Studio deployment will include our form as a feature. So, let’s copy the ID to the
clipboard now. Click File > Properties as shown in Figure 84.
Figure 84
As shown in figure 85, copy the ID to the clipboard (and then to NotePad and save it).
We will be pasting it into the workflow.xml file in Visual Studio when the time comes.
Page 69 of 109
Our form is now finished. We can now close the InfoPath application.
Page 70 of 109
We can now turn our attention to our workflow project in Visual Studio 2008.
5.1 Create the project
Figure 86
Figure 87
As shown in Figure 88, configure the local site to where the workflow will be used. In
this author’s case, it was his Team Site.
Figure 88 – The author chose his Team Site to use for debugging
Page 73 of 109
As shown in Figure 89, select Happy Birthday Card Library from the dropdown list
titled Library or List.
Page 74 of 109
Figure 89
As shown in Figure 90, make sure the checkboxes appear as shown. We need to have
the workflow started by either of the two means denoted by the check boxes. Click
Finish and our workflow project is created.
Page 75 of 109
Figure 90
5.2 – Add activity components from the Toolbox onto the design surface
We now need to drag some of the workflow components from the toolbox onto the
design view. We are going to create a task with a content type, so we need to drag
CreateTaskWithContent type object onto our design view. Drag the other objects
onto the design view until we have a design view that looks like Figure 91.
Figure 91
Note: Each box on the form represents an activity. Blue activities are method
activities and green activities are event handler activities.
Note on correlation tokens: Each blue and green box (a.k.a activity) on the design view
must have a correlation token property set. The important point is that the correlation
token for the OnWorkFlowActivated activity must NOT be the same as the other
activities on the form (be they blue or green). So, when we set our correlation tokens,
we will create a new correlation token name for all the activities OTHER than the
OnWorkFlowActivated activity.
Page 76 of 109
To set a correlation token for each activity, we right click on it, and select Properties
from the drop down menu as shown in Figure 92. The procedure is the same for all the
activities with the only difference being the name of the token we will enter. We will
therefore have two tokens in play:
In the properties window for each of the activities (other than onWorkflowActivated1),
we will type in the name mainToken. Figure 93 and 94 show the results. Note that we
need to type (or cut and paste) in the name mainToken four times, once for each
activity below onWorkflowActivated1.
Page 77 of 109
Note: If your Visual Studio 2008 does not show the Create GUID menu item as shown
in Figure 95, refer to Appendix B on how to get it to show up on the menu.
Figure 95
Click the radio button as shown in Figure 96 and click Copy. Then paste the GUID into
the taskId property as shown in Figure 97.
Page 79 of 109
Figure 96
Figure 97
Page 80 of 109
5.5 – Assign the GUID from Figure 97 to the bottom 3 activities in the design view
The procedure we denote here will be repeated three times: one for each of the
remaining activities (E.g. onTaskCreated1, onTaskchanged1 and completeTask1).
As shown in Figure 98, click on the ellipsis adjacent to the TaskId property for
onTask1created.
Page 81 of 109
Figure 98
On the resulting dialog under the tab Bind to an Existing member, under
createTaskWithContentType1, click on TaskId. Refer to Figure 99.
Page 82 of 109
Figure 99
Click OK, and the TaskId will now be associated with the taskId GUID from
createTaskWithContentType1. Repeat this process for the remaining two activities.
So, when the flow gets to the whileActivity1 activity box, what will cause the while loop
to exit? At this point, we have to configure the activity to exit by means of a rule. The
rule we will use is to exit when the value of a variable (which we will create in a
moment) changes from false to true. The variable must be created in the C# code
behind and we will name the variable _taskCompleted. Refer to Figure 100 which
shows that we added C# code for the variable.
Page 83 of 109
Now we can set the rule for the while activity. As shown in the property pane for
whileActivity1, click on the dropdown and click on Declarative Rule Condition.
Figure 101
Then, type in the name whileCondition1in the ConditionName field and then click
on the resulting ellipsis as shown in Figure 102.
Page 84 of 109
In the resulting dialog box, type in !_taskCompleted for the condition as shown in
Figure 103. Then click OK.
Figure 103
At this point, try building the solution. You should get a successful build with no errors.
5.8 – Add method stubs for the five activities in the design view
Now we need to add the method stubs that we will add code to in the code behind. For
each of the activities, double click on each activity in design view. This will cause the
stubs to be created for us behind the scenes as shown in Figure 104.
Page 85 of 109
The entire code for the workflow is shown in Appendix C. You can cut and paste the
code into your solution. At this point, we will discuss the code blocks in turn.
5.9.1 - onWorkflowActivated1_Invoked
private void onWorkflowActivated1_Invoked(object sender, ExternalDataEventArgs e){
workflowId = workflowProperties.WorkflowId;
}
In the code created for us by Microsoft, we see that the workflowId is assigned a
default GUID when the variable id declared. We really want that workflowID to be
assigned to the same value as the workflowPropertiesId.
Page 86 of 109
5.9.2 – createTaskWithContentType1_MethodInvoking
Figure 105
This function is called when the task with content type is created. Here we want to
retrieve the Happy Birthday Card Word document from the workflowProperties list. It
should be noted that there is only one item on the list since we have only 1 document in
play at this time (E.g. our Happy Birthday Card document). Recall that the creator of
the card typed in the Assigned To Login Name property in the Word document. We
need to extract that name here and assign the name to a person who must perform the
workflow task. We see that done on line 52. Once we have the assigned to name, we
can create the SPWorkflowTaskProperties object required by the workflow. This is
done on line 54. By specifying the person assigned to the task, the task to sign the
birthday card will show up on that persons Task List in SharePoint.
Page 87 of 109
5.9.3 – onTaskChanged1_Invoked
Figure 106
This function is called when the task changed event fires. For our workflow, this event
is fired when the signer of the card submits the InfoPath form we created. This function
contains the main code for the workflow.
Lines 92 and 93 fetch the From and Message strings from the InfoPath form. Note that
we have to cast the ExternalDataEventArgs to SPTaskServiceEventArgs to get at
these two fields. Next, on line 98, we try to find our Happy Birthday Card Word
document as a SPListItem object. Recall that we need to populate two fields on the
Happy Birthday Card Word document that were left blank by the creator of the card.
The two fields are From and Message (which we just got from the InfoPath form on line
92 and 93). So, we assign those fields to the birthday card on lines 108 and 109.
Now, since the task is completed by the signer, we also set the Completed field in the
Word document as well on line 110. Note that this has nothing to do with the
Completed text box in the InfoPath form. The explanation of how the Completed text
box works to disable the Submit button in the InfoPath form is given in Appendix D.
Note that we set the _taskCompleted variable to true on line 111. Recall we set the
while loop condition so that the while loop exits when this value becomes true. This is a
critical line of code.
Page 88 of 109
5.9.4 – completeTask1_MethodInvoking
Figure 107
This function is called when the while loop exits. All we want to do here is to start the
process of emailing our Happy Birthday Card to the intended recipient. So, we simply
loop through the items in the library (there is only one in this example) and find our word
document. Recall that the missing fields were populated in Figure 106. When we find
the document, we pass it to our SendEmail() function.
Page 89 of 109
5.9.5 – SendEmail
Figure 108
This function is used to send the Word Happy Birthday Card to the intended recipient.
Note that our Word document is passed to this function as a SPListItem item, not a
Word document object. Also, notice on line 197 that we obtain the reply to email
address from SharePoint. We configured this in Central Administration >
Operations > Outgoing email Settings as shown in Figure 109. Also, notice on line
199 that we specify the mail server to be that configured in SharePoint. In figure 109,
that main server is the Outbound SMTP Server, smtp.live.com (which is the mail
server for hotmail). The issue with using Hotmail as the mail server is that the hotmail
mail server is locked down to prevent spam. So, to use it, you have to provide
authentication. The authentication credentials are your hotmail username and
password. To provide these credentials, we use the NetworkCredentials object
(which is also used with many other things in .NET such as web service proxy
authentication). Also note that we are specifying to use SSL on line 205.
Page 90 of 109
Figure 109
Page 91 of 109
5.9.6 – LogToEventLog
Figure 110
To provide logging to the Windows event log, we create our own log named
HBEventLog. We navigate to the event log in Windows 2003 by clicking Start >
Administrative Tools > Event Viewer. Our log is shown in Figure 111.
Figure 111
So, what is the mechanism by which our InfoPath form is deployed, and what is the
mechanism which causes it to be displayed in a SharePoint web page when the signer
of the birthday card clicks on the task Please sign this birthday card? (See Figure 6).
The answer is in the files workflow.xml and feature.xml. Refer to Figure 112 which
shows our Visual Studio project.
Figure 112
There are 3 main steps required for the InfoPath form configuration. They are:
The first thing to notice is that we have included our InfoPath form .xsn file in the
project. The name is HappyBirthdayForm_one_Published_deployed.xsn . This
form will be deployed as a feature when we click on the Build > Deploy HBWorkflow
in Visual Studio. However, we will need to perform steps 2 and 3 for this to happen.
Page 93 of 109
The important thing here is that we include the .xsn file in the project. Recall from 4.15
that we published our .xsn file to a directory on disk. Simply include that file in the
Visual Studio project (right click HBWorkflow > Add > Existing Item ).
Appendix E has the code for the workflow.xml file as does Figure 113.
Figure 113
There are 2 lines of interest here. They are lines 11 and 15.
Line 15 – Remember when we copied our InfoPath form ID to the clipboard in Figure
85? That is the ID code we put on this line. The other thing to note is the name of the
xml tag we place it in. The tag is <Task0_FormURN> and this tag denotes what form
should be displayed for the first Task in our sequential work flow (note the character ‘0’).
In our case, the first task is defined in the blue box createTaskWithContentType1 in
design view. In our case we only have one task, so the Xml tag will be
<Task0_FormURN> . What if we had a 2nd task object in design view and wanted a
different InfoPath form to be displayed for that task? Well, we would define a new Xml
tag <Task1_FormURN>. Notice that we incremented the number by 1. So, for every
task in design view for which we need to show an InfoPath form, we add an XML tag but
increment the number by 1. This is actually explained in the notes in the default
Workflow.xml, but we have removed those notes for brevity.
The feature.xml file code is in Appendix F and in Figure 114. The key line here is
line 13.
Page 94 of 109
Line 13 specifies the .xsn file that should be deployed as a feature. When we deploy the
project using the Visual Studio deployment menu item, the InfoPath form will be
installed as a feature for us. Additionally, it will show up in Central Adminstration >
Application Management > ManageFormTemplates as shown in Figure 115.
We can verify this by going to c:\program files\common files\microsoft
shared\web server extensions\12\TEMPLATE\FEATURES\HBWorkflow and
seeing the .xsn file there after the deployment takes place. See Figure 116.
Figure 114
Figure 115
Page 95 of 109
Page 96 of 109
Figure 116
CONCLUSION
We now have all the pieces in place. You can debug the program, or simply run it.
Visual Studio will deploy the project each time you debug, so you don’t have to perform
a 2 step process of build and deploy. Also, you should be able to exercise the program
as denoted in the Figures at the beginning of this tutorial.
Hopefully, students of this tutorial will find this tutorial helpful in understanding some of
the things we went over in class. Also, feedback is encouraged.
END DOCUMENT
APPENDICES FOLLOW
Page 97 of 109
APPENDIX A
Figure
Note: On the resulting page shown in Figure, the Content Types section is shown. If it
does not show on your screen, you need to click Advanced Settings on the page and in
the resulting page click on the Yes radio button for Allow management of content
types as shown in Figure
Figure
Page 98 of 109
APPENDIX B
APPENDIX C
#region using
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Mail;
using System.Workflow.Activities;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
using System.Reflection;
#endregion
namespace HBWorkFlow
{
public sealed partial class Workflow1 : SequentialWorkflowActivity
{
#region Declarations
private const string _formatedErrorMsg = "{0} : {1}";
private const string _errorTitle = "Happy Birthday Workflow : ";
private bool _taskCompleted;
public Guid workflowId = default(Guid);
public SPWorkflowActivationProperties workflowProperties = new
SPWorkflowActivationProperties();
#endregion
#region Constructor
/// <summary>
/// Default Constructor
/// </summary>
public Workflow1()
{
InitializeComponent();
}
#endregion
#region createTaskWithContentType1_MethodInvoking
/// <summary>
/// Event handler called when createTaskWithContentType
/// event is triggered.
/// </summary>
private void createTaskWithContentType1_MethodInvoking(object sender,
EventArgs e)
{
try
{
SPList list = workflowProperties.List;
foreach (SPListItem item in list.Items)
if (item.Name.ToUpper().Equals("HAPPY BIRTHDAY.DOCX"))
{ //Address hard coding this later on
birthdayCardWordDocument = item;
break;
}
Page 100 of 109
if (birthdayCardWordDocument == null)
throw new SPException("Could not find Word document named
'Happy Birthday.docx' on the list in the library. Unable to proceed.");
string assignedTo =
birthdayCardWordDocument.Properties["Assigned To Login Name"] as string;
createTaskWithContentType1.TaskProperties = new
SPWorkflowTaskProperties
{
AssignedTo = assignedTo,
Title = "Please sign this birthday card!",
Description = "InfoPath form presented to a signer of
a Birthday Card",
DueDate = DateTime.Now.AddDays(7)
};
}
catch (Exception ex){
LogToEventLog(_errorTitle,
String.Format(_formatedErrorMsg,
MethodBase.GetCurrentMethod(), ex.Message),
EventLogEntryType.Error);
}
}
#endregion
#region onWorkflowActivated1_Invoked
private void onWorkflowActivated1_Invoked(object sender,
ExternalDataEventArgs e){
workflowId = workflowProperties.WorkflowId;
}
#endregion
#region onTaskChanged1_Invoked
/// <summary>
/// Event handler called when task is changed. This will occur
/// when the user submits our InfoPath form.
/// </summary>
private void onTaskChanged1_Invoked(object sender,
ExternalDataEventArgs e){
try{
SPListItem birthdayCardWordDocument = null;
string from =
((SPTaskServiceEventArgs)e).afterProperties.ExtendedProperties["From"] as
string;
string message =
((SPTaskServiceEventArgs)e).afterProperties.ExtendedProperties["Message"] as
string;
if (String.IsNullOrEmpty(from) ||
String.IsNullOrEmpty(message))
Page 101 of 109
if (birthdayCardWordDocument == null)
throw new Exception(" Birthday Card Word document list
item was null.");
birthdayCardWordDocument.Properties["From"] = from;
birthdayCardWordDocument.Properties["Message"] = message;
birthdayCardWordDocument.Properties["Completed"] =
"Completed";
_taskCompleted = true;
birthdayCardWordDocument.SystemUpdate();
}
catch (Exception ex){
LogToEventLog(_errorTitle,
String.Format(_formatedErrorMsg,
MethodBase.GetCurrentMethod(), ex.Message),
EventLogEntryType.Error);
}
}
#endregion
#region LogToEventLog
/// <summary>
/// Our function to log to the Windows Event log
/// </summary>
/// <param name="errorTitle">The title to be applied to the
/// error entry.</param>
/// <param name="message">The message to appear in the event
log.</param>
/// <param name="msgType">The message type (E.g. Error, information,
etc.</param>
public static void LogToEventLog(string errorTitle, string message,
EventLogEntryType msgType){
EventLog hblog;
try{
if (!EventLog.SourceExists("HBEventLogSource"))
EventLog.CreateEventSource("HBEventLogSource",
"HBEventLog");
using (hblog = new EventLog { Source = "HBEventLogSource" })
hblog.WriteEntry(errorTitle + " : " + message + " : " +
msgType);
}
catch { }
}
Page 102 of 109
#endregion
#region completeTask1_MethodInvoking
/// <summary>
/// Event handler called when the completeTask event is fired.
/// </summary>
private void completeTask1_MethodInvoking(object sender, EventArgs e){
try{
SPList list = workflowProperties.List;
foreach (SPListItem item in list.Items)
if (item.Name.ToUpper().Equals("HAPPY BIRTHDAY.DOCX")){
//Address hard coding this later on.
happyBirthdayWordDocument = item;
break;
}
SendEmail(happyBirthdayWordDocument);
}
catch (Exception ex){
LogToEventLog(_errorTitle,
String.Format(_formatedErrorMsg,
MethodBase.GetCurrentMethod(), ex.Message),
EventLogEntryType.Error);
}
#endregion
#region SendEmail
/// <summary>
/// Function used to send the Happy Birthday Card Word document
/// as an attachment.
/// </summary>
/// <param name="happyBirthdayWordDocument">Our Birthday Card as a
SPListItem.</param>
private void SendEmail(SPListItem happyBirthdayWordDocument)
{
try
{
Page 103 of 109
#endregion
}
}
Page 104 of 109
Recall that in Figure 61, we specified that the button should be disabled whenever the
text in the Completed text box is set to the number “1”. Nowhere in our code do we see
that we as programmers have set this value. In fact, the default value of “0” in the text
box when the form is loaded does not come from our C# code either.
In the topic 4.11 – Configure the form to receive data from the workflow on
load, we specified a secondary data source to receive data from the host whenever the
InfoPath form loads. We also specified that the ItemMetaData.xml file be used as the
secondary data source on receive. If we exercise the sending of the Happy Birthday
Card workflow, we see that the InfoPath form contains a value of “0” on first load.
However, when the form is submitted, invoking the form a 2 nd time shows the value has
been set to “1”. Again, we as programmers had nothing to do with these values
appearing in the text box.
This happens because we use the name ows_Completed in the Xml file and reference
it in the properties of the Completed text box on the form as shown in Figure A1. This
tag appears to be linked to a Boolean value denoting whether or not the task is in the
completed state. This goes on behind the scenes and appears to be an undocumented
feature from Microsoft.
Page 105 of 109
Figure A1
If we tried to substitute a different ows_ value, such as ows_Done (or other name of
your choice), then the mechanism will not work, mainly because we cannot set the
value to “1” in the C# code behind after the task completes. The key point here is that
we would need to set the value after the task completes, but that is too late in the flow to
change it.
As an alternative, if we were to attempt to set a value in the Completed text box before
the task completes, say in the createTaskWithContentTpe1_MethodInvoking
event handler, then it would work. The value “1” would appear in the InfoPath form.
However, the user would be helpless since he/she could not submit the form since the
submit button would be disabled on first load! The form is dead from the outset. Not
good. We want the “1” to appear on the subsequent loads, but again, that is after the
task is completed.
To set the value of the form on first load (which is useless for us as mentioned above)
would require that we include a new property in our C# code called
createTaskWithContentType1_TaskProperties since that would give us access to
the extendedProperties object. Our code would look like Figures A2 and A3.
So, where does this leave us to disable the submit button after the task completes?
Well, we must use the undocumented feature where the Boolean value of “0” and “1” is
Page 106 of 109
provided for us by using ows_Completed as the name in the ItemMetaData.xml file and
specified in the binding for the Completed form.
It would be great if readers of this tutorial could research this further and find
documentation on this phenomenon.
Page 107 of 109
Figure A2
Figure A3 – This line would set the value in the InfoPath form on 1 st load.
Page 108 of 109
APPENDIX E – workflow.xml
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Workflow
Name="HBWorkFlow"
Description="My SharePoint Workflow"
Id="68cfd787-087f-4baf-8718-00666bd6afd4"
CodeBesideClass="HBWorkFlow.Workflow1"
CodeBesideAssembly="HBWorkFlow, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=c588e451cdda9911"
TaskListContentTypeId="0x01080100C9C9515DE4E24001905074F980F93160">
<Categories/>
<MetaData>
<Task0_FormURN>urn:schemas-microsoft-
com:office:infopath:HappyBirthdayForm-one-Published-deployed:-myXSD-2009-09-
15T14-10-01</Task0_FormURN>
<StatusPageUrl>_layouts/WrkStat.aspx</StatusPageUrl>
</MetaData>
</Workflow>
</Elements>
Page 109 of 109
APPENDIX F – Feature.xml
<?xml version="1.0" encoding="utf-8" ?>
<Feature Id="648c9208-3138-4bf3-acd4-06b7e5e51b11"
Title="HBWorkFlow feature"
Description="My SharePoint Workflow Feature"
Version="12.0.0.0"
Scope="Site"
ReceiverAssembly="Microsoft.Office.Workflow.Feature, Version=12.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c"
ReceiverClass="Microsoft.Office.Workflow.Feature.WorkflowFeatureReceiver"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="workflow.xml" />
<ElementFile Location="HappyBirthdayForm_one_Published_deployed.xsn"/>
</ElementManifests>
<Properties>
<Property Key="GloballyAvailable" Value="true" />
<Property Key="RegisterForms" Value="*.xsn" />
</Properties>
</Feature>