In a previous post I have talked about an approach of transferring complex data between the workflow and the InfoPath task form.
Now in this post I will give you the implementation described before and give you instructions on how to install and use it.
Download the following IPTaskFormsMadeEasy.zip and extract it on your development machine. It contains all the necessary files to follow the steps described here.
General Description:
The idea here is to bypass the ItemMetadata.xml file and send the xml that the form will de-serialize and display.
For this I have created a new Content Type derived from the Out Of the Box InfoPath Content Type. The ID for this Content Type is “0x01080100C9C9515DE4E24001905074F980F93161”. When used this content Type will open the InfoPath Task Form but instead of processing the ItemMetadata.xml will read a special key value pair ("IPFormDataXML”) from ExtendedProperties that will contain the xml for that task. Once this task form gets closed the xml data for this task gets serialized back into the special key value pair to be read by the Workflow.
By using this approach we can pass quite complex data, as repeating tables, between the workflow and the task form without passing every value separately using the ItemMetadata.xml. Another benefit would be that you can now validate the data types using the task xsd and have data type safety when transferring information between the Workflow and the task form.
Installation:
Is quite simple to install this in your farm and does not affect any existing workflows as we are using our own content type.
Inside the zip file you will find the MFDWorkflowIPTask.wsp SharePoint solution that you will need to install and deploy into your farm using the STSADM and or Central Admin site. This will install a site collection feature that will enable a new content type (MFD.WorkFlowIPTaskCtype ContentType)
Now enable the MFD.WorkFlowIPTaskCtype feature on the site collection where you will be using Workflows that needs this content type and your done with the installation.
After this nothing actually happen until you are using the new content type into your workflow.
Developer Instructions:
Each time when this content type is used in Workflows the _layouts/MFD/WrkTaskIP.aspx page will be used instead of the OOB one (_layouts/WrkTaskIP.aspx).
I am not going to show here how you use the ItemMetadata.xml file to pass data between the workflow and the task form but I have included a demo with source code into the zip file, ApprovalWF1 project, that shows how to use the ItemMetadata.xml and ExtendedProperties. The demo uses just a single task form but imagine that you might have a complex workflow with over 10 or 20 tasks and you can see how hard is to maintain that code as changes are needed to the Task Forms.
The ApprovalWF2 demo project shows you how to use the new approach by passing the full xml to the task form and again being a demo I am just showing you how to implement it for a single task.
This Approval workflow deal with an Expense Report that contains complex data including repeating tables. Think about the following requirement where you are asked that your workflow tasks needs to contain all the data from the original expense report so that the approver does not need to open the original report and the approvers can’t see other approvers notes during the approving process. If you want to know why you can’t use the Expense Report form and just add fields for each approver and just let them deal with that form drop me a line.
So now for the solution on how you implement this requirement.
First the forms, Expense Report Form and the the Task Form are both InfoPath forms. There are two ways of building InfoPath forms:
- Just start adding the fields as needed: very good for POC or if you don’t need much control over the fields namespace, schema and types.
- Start by thinking of the InfoPath like you do for you DB starting with the schema first by manually building your xsd files and then attach them to your InfoPath forms. This will let you control everything including the sharing of data types between multiple forms. This is how I recommend of building any Enterprise level InfoPath Form.
Under the Forms folder you have both, the Expense Report Form and ExpenseReportApproveTaskForm2, including the schema files. To include all the fields from the Expense Report Form (ERF) into the Task Form all you need to do is add the following tags:
<xsd:import namespace="http://schemas.microsoft.com/office/infopath/2003/expXSD"
schemaLocation="ExpenseReport.xsd"/> this will include all the fields defined into the ExpenseReport.xsd that is used by the Expense Report Form;
<xsd:element ref="exp:expenseReport" minOccurs="0"/> that will create an element into the ExpenseReportApproveTask2 Form for all the data included in the Expense Report Form.
Now having the xsd schema files for both of the forms we can actually generate two classes that will match that schema using:
xsd.exe ExpenseReport.xsd ExpenseReportApproveTask.xsd /c
/namespace:MFD.SharePoint.WorkFlow.Expenses.FormData
We will be using those two classes to serialize and de-serialize the xml data for both the Expense Form and Task Form.
Reading Expense Report Form inside workflow:
- Reading Expense Report Form inside workflow:
[NonSerialized]
private expenseReport _expense;
public expenseReport Expenses
{
get
{
if (_expense == null)
{
//Read the Form into memory
XmlSerializer serializer = new XmlSerializer(typeof(expenseReport));
using (Stream stream =
this.workflowProperties.Item.File.OpenBinaryStream())
{ _expense = serializer.Deserialize(stream) as expenseReport; }
}
return _expense;
}
}
- Create the Task:
private void CreatingApproveTask(object sender, EventArgs e)
{
ApproveTaskId = Guid.NewGuid();
ApproveTaskProperties.AssignedTo =
Expenses.manager.managerEmailAddress;
ApproveTaskProperties.SendEmailNotification = true;
ApproveTaskProperties.Title = string.Format("Approval for: {0}",
Expenses.employee.name);
expenseReportApproveTask task = new expenseReportApproveTask();
task.Decision = string.Empty;
task.Notes = string.Empty;
task.expenseReport = this.Expenses;
ApproveTaskProperties.TaskType = 0;
ApproveTaskProperties.ExtendedProperties[IPFormDataXMLTag] =
Utility.Serialize(task);
}
- Read the Task:
private void ApproveTaskChanged(object sender, ExternalDataEventArgs e)
{
string IPFormDataXML = this.ApproveTaskAfterProperties.ExtendedProperties
[IPFormDataXMLTag] as string;
expenseReportApproveTask task = Utility.Deserialize(IPFormDataXML);
string taskDecision = task.Decision;
isFinished = !string.IsNullOrEmpty(taskDecision);
if (isFinished)
{
ApproveTaskOutcome = taskDecision;
}
}
This should give you an idea on how easy and clean is the code now. You will not have to change this code just because somebody else decided to the rename one of the fields in the Expense Report Form or even added a new field. The code will be the same what changes are the generated classes that will need to be refreshed but that is a recompilation matter not a code change.
The other thing that you should observe here is that we do not use strings anymore to pass data but the class itself which is strong typed so there is no more need of parsing the string into another type like Boolean and hope that nobody change the type on you.
Adding new Task Form, no problem, they follow the same pattern so adding another 10 or 20 forms to the Workflow is a breeze and reduce the spaghetti code needed to wire those up as you will have a different generated class for each.
The only thing that I did not show yet is how to use the new content type and that is easy, just modify the elements.xml for the workflow to specify that the tasks should use the new content type using the:
TaskListContentTypeId="0x01080100C9C9515DE4E24001905074F980F93161"
As you are going to deploy this is production i would recommend to add the MFD.WorkFlowIPTaskCtype feature as a dependency to your workflow feature by adding the following tag inside the Feature tag.
<ActivationDependencies>
<ActivationDependency FeatureId="33cf18b1-e091-46c2-9f52-114516731db7"/>
</ActivationDependencies>
By doing this the system will automatically activate the dependency feature and will fail to activate your feature if the required feature is missing.
Hope this helps to clarify this approach but if not or need more info don’t hesitate to contact me or leave a comment.