Stumbling Through: K2 [blackpearl] - Infopath Integration (Part II)
Previously, I identified a couple of specific technical problems that I'd like to stumble through solutions for. One of the technical problems I described like this:
'Submitting one order form may spawn multiple processes that can be acted on simultaneously by different users to accomplish one final result. How will we have one process split into many, passing along the common information necessary to the child processes?'
I suppose I will clarify this description a little bit before I jump into the solution. One InfoPath form containing some common user-entered data starts the master process. Submitting this form starts three other processes that have their own InfoPath forms that need the common information pre-populated from the first InfoPath form. So, to do a proof of concept for this issue, we'll need a total of four InfoPath forms and four processes defined in a K2 workflow project (Master, Child1, Child2, Child3). Let's start out with the InfoPath forms:
Create an InfoPath form named 'Master', and add two text fields that will represent the common data. We can just call them 'Id' and 'Name':
Create the next three InfoPath forms named 'Child1', 'Child2' and 'Child3'. You can put any other information you want in them, as long as you include the 'CommonFields' section as it was defined in the Master form:
Now that we have our InfoPath templates defined, we need to consume them in K2 Processes. Start Visual Studio and create a new empty K2 project:
Add four processes to the project named 'Master', 'Child1', 'Child2' and 'Child3':
Let's start with defining each child process. They will simply create a task list item pointing to their corresponding InfoPath form which will have the header somehow pre-populated with data entered in the Master form. First things first, though, we need to integrate the process with its InfoPath form. Drag out an InfoPath Integration process wizard and follow the steps necessary to integrate the InfoPath form and create a simple client event (I've blogged this process before if you get stuck, you can read it here, though that was blogged long before I semi-knew what I was doing). Duplicate the process for all three child processes so that they look as simple as this:
Note that we did nothing with pre-populating the data in the InfoPath form yet, we'll tackle that later. For now, we can move on to our Master process. We'll need to integrate the Master InfoPath form with this process similarly to how we did it with the Child processes, the only difference being that we need to ensure that the Master form starts the process, by filling in this portion of the integration wizard:
This will make it so that any time this specific form is submitted, it will automatically start this Master process. So now we come to the heart of the matter - how do we invoke the child processes all at once? Thankfully, the developers of this product anticipated this very need and supplies a wizard that will make this challenge a breeze. Drag out the 'IPC Event' wizard:
In the wizard, we'll need to specify which process to invoke. Let's start out invoking our Child1 process. It also asks us if it is Synchronous or Asynchronous - basically whether or not we need a response from this child process before we can continue. In our case, we just want to kick off the process and we don't care what it does so we will chose Asynchronous:
We also have the ability here to specify a new folio, if we want the child folio to match something that we are gathering in the parent process. Be warned, though, that this folio definition will be overridden by any folio definition in the Child process (defined in the InfoPath Integration wizard). Clicking next brings us to a simple security question - who to call the process as. Now normally, I would expect to use 'Integrated Windows' here so that the process is invoked by whomever invoked the Master process. However, for reasons I don't yet understand, this never worked successfully for me and I've had to use the 'Impersonate Originator' option:
Clicking next again brings us to a screen that is very interesting to us, given the challenge we are trying to overcome. It is asking us what data we want to pass to the child from the parent! That is perfect! Or is it... Let's see how it handles our specific request of passing the 'CommonFields' data from the Master form to the 'CommonFields' data of the child. Add a new process field mapping:
Use the object browser to drill into our Master infopath form XML representation and select the 'CommonFields' section:
And drag it onto the Field Mapping form:
Now use the object browser via ellipsis next to the Child process field name to browse to the 'CommonFields' data in the Child InfoPath form:
And drag it onto the Field Mapping form... uh oh, it won't let us! Looks like we are asking it to do something a little more complex than what it was designed for, which is understandable as moving one piece of xml between documents might not always be very straight forward, particularly when namespaces are involved. Looks like we'll have to do some of this ourselves via code. Bring up the Child Process object browser again, and add a new xml field called 'ParentCommonFields'. We'll use that as the temporary container for the common field data:
Drag it onto the Field Mapping form:
Something else I learned the hard way about this process is that if you specify a portion of an xml document, as we did when we specified 'CommonFields' in the Master document, it will send the data as a list of field values as opposed to the XML. As such, we'll just send the whole XML over and parse out the CommonFields section in the Child process logic. So revisit the Parent field value and browse to the Master xml document:
Drag it onto the Field Mapping form:
Repeat this IPC event process for all three Child processes so your Master process looks like this in the end:
Now we need to revisit each Child process and add some logic to copy the parent common fields into the child common fields InfoPath xml. The only way to do this is with some server side code, so drag out a new 'Default Server Event (Code)' wizard:
And connect it up before the InfoPath client event:
View the code of this Server Event (Right-Click - View Code - Event Item) and add a 'using System.Xml' to the top to make our XML references cleaner. Now, what we need to do is load up a DOM with the Child InfoPath form XML so we can navigate to its 'CommonFields' section and replace it with the 'ParentCommonFields' value. The caveat here is that the Parent Common Fields XML may have a different namespace associated with it, so we'll need to load that up in a DOM too so we can identify and replace any occurrences of an incorrect namespace:
XmlDocument docChild = new XmlDocument();
docChild.LoadXml(K2.ProcessInstance.XmlFields["Child1"].Value);
XmlNamespaceManager nsMgrChild = new XmlNamespaceManager(docChild.NameTable);
nsMgrChild.AddNamespace("my", docChild.DocumentElement.GetNamespaceOfPrefix("my"));
XmlDocument docParent = new XmlDocument();
docParent.LoadXml(K2.ProcessInstance.XmlFields["ParentCommonFields"].Value);
XmlNamespaceManager nsMgrParent = new XmlNamespaceManager(docParent.NameTable);
nsMgrParent.AddNamespace("my", docParent.DocumentElement.GetNamespaceOfPrefix("my"));
docChild.SelectSingleNode("//my:CommonFields", nsMgrChild).InnerXml = docParent.SelectSingleNode("//my:CommonFields", nsMgrParent).InnerXml;
K2.ProcessInstance.XmlFields["Child1"].Value = docChild.InnerXml.Replace(nsMgrParent.LookupNamespace("my"), nsMgrChild.LookupNamespace("my"));
Let's deploy this thing, and when it is done, we will visit the SharePoint list associated with our 'Master' form, add an item, and see if we get tasks generated for each 'Child' form. Opening up those tasks should show the Master Common Field data in the header. Don't forget to give yourself process start rights for the master and child processes via the Workspace! If it doesn't work as expected or I glossed over an important detail, please leave me a comment and I'll try to help as best i can.