Introduction
Sorry for the late release of this part three of this series. A new child birth, holidays and work just threw my schedule off…
This is the third part of a continuing series on how to build human workflow with the Windows Workflow Foundation, MOSS and InfoPath. Up to this point I have discussed some design considerations and assumptions you should make before building a workflow in Visual Studio. Then I also had a deep discussion on how create the Visual Studio solution and assumptions when creating it.
In this posting, I am going to show you all the things that you need to do to create the InfoPath forms that will be used for this process. The nice thing about using InfoPath is that you can mock-up the forms, use them to help elicit requirements from users and the form will be re-usable when you move to construction.
When to Use InfoPath
I am a broken record on this topic on when to use InfoPath or not. InfoPath can be a great tool if you recognize its limitations. Please read this posting for more information about the assumptions and considerations you should make before using InfoPath. I always believe that if I can avoid doing .NET managed code, the data is relatively flat and you do not require a complex User Interface InfoPath is great. I really like InfoPath forms for simple forms that gather data, some decisions are made on that data and then it is persisted off to a database somewhere. Data validation is extremely easy and it is a no brainer to wire up a web service to get or set data.
InfoPath Schema Best Practices
I am a big believer in keeping the schema of your InfoPath form clean. You could go as far as create your schema externally and then import into the InfoPath form but it can be annoying to deal with at times; especially if you have to re-import the schema every time you want to add a field to the form.
Before building your InfoPath form, I would suggest adding the following three groups to the root of the schema. In the Main datasource add the following three groups: Business, Workflow, and InfoPath.
- First the Business group has a generic name. I recommend naming something like PurchaseOrder, TravelRequest, etc. All data fields associated to the form itself should be located here.
- Second the Workflow group will be used to store all data that is associated to the windows workflow. There will be pieces of data that you may want to have in the form which has nothing to do with the business data; they should be placed here.
- Third is the InfoPath group which is used to store any fields that nothing to do with either business or the workflow. Rather they are used for functionality of the InfoPath form. When doing InfoPath forms in the past I have had situations where I had to add checkboxes, radio buttons, etc. to improve the user experience. The result of which would litter my schema with data elements. This why I recommend creating this group. InfoPath does not use a control based event model like ASP.net or WinForms. Events are risen from the XML document based on when a value changes. This results in the creation of data field; many view this as a limitation.
Even if I was not building a MOSS WF workflow, I would still structure my schema in a similar manner. Nothing is more frustrating that see an InfoPath form with a schema that is not well thought out.
The Forms in the Solution
In this solution there are four forms: request, association, initiation and task forms. In the first posting of this series, I discussed how the forms interact with each other and many design assumptions. The most important is that the request form is different that the association, initiation and task forms. The request form should be treated just like any piece of generic content in MOSS while the association, initiation and task forms are used for the business process for that piece of content.
When building the forms themselves, there is really nothing terribly special that has to be done. I usually like to try to get the forms built up minimally before I start trying to build a WF workflow and getting everything all wired up. As such, that is what you will see in the rest of this blog.
Request Form
You may recall in the second posting I created a solution for this workflow. One of the projects was called WFDistillery.MyProcess.Form.Request which is the form that the user will submit. For this blog series, I am going to keep the InfoPath form simple; I really want to focus on the best practices of how to do workflow in general. We will call this a Software Request Form.
Here are a couple of tasks that you will need to knock out (for all of the forms):
- Tools >> Form Options >> Compatibility >> and allow the form to be opened in a browser.
- Since I am building these InfoPath forms within Visual Studio, they will have .net managed code. Because of that you need to make sure that you go to Tools >> Form Options >> Security and Trust and give the form Full Trust. You will need to assign a certificate. Since the form will be running on the server side as a web enabled form, you can get by with using a self-signed certificate. Moving forward, you will need to deploy this form through SharePoint Central Administration or read this posting to deploy the form as a Feature. I will go into more details later.
Next I need created the main data source of the form. Using the recommendation above, the following is the main data source for the form. Pretty simple:
- Request section has fields associated to the software request only. Note that Status field has default value of "Not Submitted".
- The InfoPath section has fields that are used for the InfoPath purposes only. Right now it only has a field called UniqueFormName that will be populated with a GUID which is explained below.
- The Workflow section has a single state field right now. This will be updated by the workflow to reflect the state in which the InfoPath form is within the workflow. The State field has a default value of "Not Started".
I am going to skip much of the presentation of the form and business rules that can be enforced with the schema like making fields required or apply regular expressions to them.
Now the next thing I need to do is get the Request Form ready for a submission. What I want to achieve is that when the form is submitted to a Form Library, that the form will be saved uniquely in the Form Library and the form will be closed on submission. To achieve this I need to add some .net managed code to generate a GUID.
We need to add the code, so in Visual Studio open the manifest.xsf file, then in the main menu select Insert >> Loading Event. An event handler will be generated. In here, I simply check the Status and State fields before generating a GUID. I set it into the UniqueFormName field which is in the InfoPath group.
public void FormEvents_Loading(object sender, LoadingEventArgs e)
{
XPathNavigator status = this.MainDataSource.CreateNavigator().SelectSingleNode(
"/my:SoftwareRequestForm/my:Request/my:Status", this.NamespaceManager);
XPathNavigator state = this.MainDataSource.CreateNavigator().SelectSingleNode(
"/my:SoftwareRequestForm/my:Workflow/my:State", this.NamespaceManager);
if (status.Value == "Not Submitted" && state.Value == "Not Started")
{
//Generate guid which will be used in the form name.
XPathNavigator formName = this.MainDataSource.CreateNavigator().SelectSingleNode(
"/my:SoftwareRequestForm/my:InfoPath/my:UniqueFormName", this.NamespaceManager);
formName.SetValue(Guid.NewGuid().ToString());
}
}
The last step I have to complete is set up the form for submission. To do this, you will need to create a form library. Later you can reconfigure the form to submit to the correct place in production. To set this up:
- Create a form library in your SharePoint development environment.
- Go to Tools >> Form Submit Options.
- Check Allow Users to Submit the form.
- Select to send the form to a SharePoint document library.
- Click the Add button next to the data connection dropdown. This will launch the Data Connect Wizard. Put in the URL to form library you have created. Then for the File name, select the UniqueFormName field that was populated in during the form load. This will ensure the name for the form is unique. Finish the Data Connection Wizard.
- Click the Advanced button and select to close the form after submission. When the workflow is configured with the form, it will be configured to start on submission of a new item into the form library. So we want to close the form immediately because the process has now begun.
At this point, the Request form is ready and now we need to configure the other forms for the workflow.
Association Form
A couple of tasks that you will need to knock out are:
- Tools >> Form Options >> Compatibility >> and allow the form to be opened in a browser.
- Tools >> Form Options >> Security and Trust and give the form Full Trust.
I am not going to do anything with this form for this process however I have it part of the solution as a placeholder. Once you have done this, you are pretty much good to go.
Remember, the association and initiation forms data sources need to have the same schema. I think this is pretty silly and does not make sense because you will need to do different things with each form. I have complained about this before in this post. Also refer to the first posting for more detailed information about the association form.
Even though I plan to no use the association form, that fact that I have it configured means that it will be shown to me when I associate the workflow to a list in SharePoint. At minimum I need to add a submit button. Unlike the request InfoPath form, this InfoPath form will be displayed within the SharePoint template and the default submit, save, save as and close buttons are not available. I need to add a submit button so that workflow can be associated. To do this:
- Add a button to the form and change the label to "Associate".
- Go to the button Rules.
- Create a new Rule.
- Add a new Submit Action.
- In the Data Connection wizard, select Submit data, then select "to the hosting environment, such as an ASP.net page or a hosting application". The hosting application is SharePoint! Then finish the wizard.
- Add a second Action to close the form.
Now you are done with the association form.
Initiation Form
This is the form that will be used when the workflow is first initiated. However, the user will never see this form given I plan to only initiate the workflow when the form is submitted into the form library. The initiation form will be shown if a user can manually start the workflow by using the action dropdown. The initiation form is really handy if you have a word document, excel spreadsheet, a list item, etc. that you are building a workflow for. Even though I plan to not use it, I will wire it up because I may want to allow the user to manually start the workflow down the road. The user experience would be the user would have to open the request form, enter all the required data, save the request form to the form library, and then the user would use the workflow actions to initiate it.
You need to knock out the same tasks as you did with the association form:
- Tools >> Form Options >> Compatibility >> and allow the form to be opened in a browser.
- Tools >> Form Options >> Security and Trust and give the form Full Trust.
- As well, you will need to add an Initiate button using the same steps as described in the association from section.
Task Form
This is the task form that will be shown to user.
Again go ahead and knock the following out:
- Tools >> Form Options >> Compatibility >> and allow the form to be opened in a browser.
- Tools >> Form Options >> Security and Trust and give the form Full Trust.
- Add a submit button as described earlier.
Now we need to add some data fields to this form. I added the following data fields to the form.
Notes:
- The RequestID is the primary key from the database I plan to be using down the road. Do plan to load in some collections of data from the database into the task form so I will need that.
- The Status and State fields will be populated by the workflow. I will be showing you how to retrieve data from the InfoPath form and then set that data into the task form.
- The view field will be populated by the workflow to show the correct view to the user based on the state of the workflow. The View field is my way of not using many task forms. In the first posting I discussed some reasons on why I do not like to have many InfoPath forms. It is a lot simpler to add many views to the InfoPath form and then in a loading event of InfoPath switch to the correct view.
- All of the action fields are fields that will be used to capture a decision.
Notice that I did not much emphasis on putting all the data fields from the main request form into the task form. Later in this series, I will show how to add a link into the task InfoPath form that will open the request InfoPath form which has all the detailed information about the request.
I will have to create a separate blog entry around getting the task form completely wired up. Specifically it will focus on providing the correct list of actions for a user based on the state and capturing "who" took an action. You may not always know who because you may assign the task to a group of users.
The last thing I want to go over is the how to populate the fields in the task InfoPath form. This is actually pretty well documented, so I will breeze over this.
- Add a file called "ItemMetadata.xml" to the Task0 project in the root. It is important to make sure that the name is "ItemMetadata.xml" and not anything else.
- In the file, remove everything from it and add the following. Note that fields are prefixed with "ows_".
<z:row xmlns:z="#RowsetSchema" ows_RequestID="" ows_State=""
ows_Status="" ows_View="" ows_Action="" ows_ActionComments=""
ows_ActionUser="" ows_ActionUserName="" ows_ActionUserEmail=""
/>
- Next, we need to add this xml document as a data source for the task form. Open the Data Connection wizard >> add a new data connection to receive data >> select from an XML document >> browse to ItemMetadata.xml >> select "Include the data as a resource file in the form template or template part" >> make sure the "Automatically retrieve data when form is opened" is checked >> finish the wizard.
- Now double click on the RequestID field in the main data source. In the properties window, go to the Default Value section >> click the fx button >> click Insert Field or Group >> select the ItemMetadata secondary data source >> select the RequestID field. Now complete this for the all of the other fields.
So you may be wondering, what is this all about. Well in your workflow code we will want to set fields like the RequestID in the task form or get it back out link in the Action field. For instance we will write code like this:
taskProperties.ExtendedProperties["RequestID"] = _requestID;
Or
string action = taskProperties.ExtendedProperties["Action"];
Basically the SharePoint Workflow Infrastructure will take values that have been set into the ExtendedProperties HastTable and load in and out ItemMetadata.xml document that resides in the InfoPath form. This is only way to push data into an out of the task form without have to create data services to be used in the InfoPath form.
--------------------------
There is an important note/rant I would like to make. The data in the InfoPath task form cannot be hierarchical, meaning the data needs to be flat so that you can access the fields of the InfoPath form within the workflow code. Here is an example many are familiar with.
string foo = taskProperties.ExtendedProperties["Foo"];
The ExtendedProperties HastTable property has the fields that you define in the main data source of the task InfoPath form. You do not have the ability to navigate deeper nor access data in a repeating nodes. I have found this to be a huge limitation of task forms. The only way around it to:
- Use web services to push data in and out of the task form. Then in the workflow access the data again through those same data services.
- Only use the task form to make a decision and capture as much complex data in the request form.
The second approach is usually what I strive for.
Both the association and initiation forms do not suffer from this issue. First because the data is always a get, because you will only ever see the association and initiation forms once in the workflows lifecycle. Second, there is a convenient properties called AssociationData and InitiationData on the SPWorkflowActivationProperties class. These will give the entire XML string of data from these two forms.
--------------------------
What Is Next
Next I plan to go over the creation of the state workflow. Then we will start getting into the fun stuff of getting data in and out of these forms, adding security to items, moving documents and attachments around, deploying the solution and adding all the little things that improve usability. Right now, I need to finish up that foundation so that you can do all of that.